//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.ServiceModel.Channels { using System; using System.Net; using System.Net.Sockets; using System.Runtime; using System.Threading; #if DEBUG using System.Diagnostics; #endif class UdpSocket { int openCount; int timeToLive; int pendingReceiveCount; Socket socket; #if DEBUG StackTrace disposeStack; #endif public UdpSocket(Socket socket, int interfaceIndex) { Fx.Assert(socket != null, "Socket argument cannot be null"); this.openCount = 0; this.pendingReceiveCount = 0; this.ThisLock = new object(); this.socket = socket; this.InterfaceIndex = interfaceIndex; this.timeToLive = socket.Ttl; } enum TransferDirection : byte { Send, Receive, } public AddressFamily AddressFamily { get { return this.socket.AddressFamily; } } public int PendingReceiveCount { get { return this.pendingReceiveCount; } } //value of UdpConstants.Defaults.InterfaceIndex (-1) if not a multicast socket internal int InterfaceIndex { get; private set; } internal bool IsDisposed { get { return this.openCount < 0; } } internal object ThisLock { get; private set; } //must be called under a lock in receive loop public IAsyncResult BeginReceiveFrom(byte[] buffer, int offset, int size, ref EndPoint remoteEndPoint, AsyncCallback callback, object state) { UdpUtility.ValidateBufferBounds(buffer, offset, size); ThrowIfNotOpen(); bool success = false; IAsyncResult asyncResult = null; try { this.pendingReceiveCount++; asyncResult = new ReceiveFromAsyncResult( this.socket, new ArraySegment(buffer, offset, size), remoteEndPoint, size - offset, this.timeToLive, callback, state); success = true; return asyncResult; } finally { if (!success) { this.pendingReceiveCount--; } } } public void Close() { bool cleanup = false; lock (this.ThisLock) { if (this.IsDisposed) { return; } //UdpUtility.CreateListenSocketsOnUniquePort can create a socket and then close it without ever calling //UdpSocket.Open() if it fails to bind on both IPv4 and IPv6. If this happens, then openCount will still be at zero. if (this.openCount > 0) { this.openCount--; } if (this.openCount == 0) { cleanup = true; this.openCount = -1; } } if (cleanup) { #if DEBUG if (!Fx.FastDebug) { disposeStack = new StackTrace(); } #endif //non-zero sendTimeout causes the socket to block on a receive while looking for an EOF, which will never come this.socket.Close(0); } } //must be called under a lock in receive loop public ArraySegment EndReceiveFrom(IAsyncResult result, ref EndPoint remoteEndPoint) { this.pendingReceiveCount--; return ReceiveFromAsyncResult.End(result, ref remoteEndPoint); } internal EndPoint CreateIPAnyEndPoint() { if (this.AddressFamily == AddressFamily.InterNetwork) { return new IPEndPoint(IPAddress.Any, 0); } else { return new IPEndPoint(IPAddress.IPv6Any, 0); } } public void Open() { lock (this.ThisLock) { if (this.IsDisposed) { throw FxTrace.Exception.AsError(new ObjectDisposedException("UdpSocket")); } this.openCount++; } } public int SendTo(byte[] buffer, int offset, int size, EndPoint remoteEndPoint) { ThrowIfNotOpen(); UdpUtility.ValidateBufferBounds(buffer, offset, size); try { int count = this.socket.SendTo(buffer, offset, size, SocketFlags.None, remoteEndPoint); Fx.Assert(count == size, "Bytes sent on the wire should be the same as the bytes specified"); return count; } catch (SocketException socketException) { throw FxTrace.Exception.AsError(ConvertNetworkError(socketException, size - offset, TransferDirection.Send, this.timeToLive)); } } public IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, EndPoint remoteEndPoint, AsyncCallback callback, object state) { ThrowIfNotOpen(); UdpUtility.ValidateBufferBounds(buffer, offset, size); return new SendToAsyncResult(this.socket, buffer, offset, size, remoteEndPoint, this.timeToLive, callback, state); } public int EndSendTo(IAsyncResult result) { return SendToAsyncResult.End(result); } static Exception ConvertNetworkError(SocketException socketException, ReceiveFromAsyncResult result) { return ConvertNetworkError(socketException, result.MessageSize, TransferDirection.Receive, result.TimeToLive); } // size: sending => the size of the data being sent // Receiving => the max message size we can receive // remoteEndPoint: remote endpoint reported when error occured static Exception ConvertNetworkError(SocketException socketException, int size, TransferDirection direction, int timeToLive) { Exception result = null; if (socketException.ErrorCode == UnsafeNativeMethods.ERROR_INVALID_HANDLE) { //This would likely indicate a bug in our ref-counting //for instance, a channel is closing the socket multiple times... Fx.Assert("The socket appears to have been closed unexpectedly. This probably indicates incorrect ref counting (i.e. a channel is closing the socket multiple times)"); result = new CommunicationObjectAbortedException(socketException.Message, socketException); } else { string errorMessage; switch (socketException.SocketErrorCode) { case SocketError.MessageSize: //10040 errorMessage = (direction == TransferDirection.Send ? SR.UdpMaxMessageSendSizeExceeded(size) : SR.MaxReceivedMessageSizeExceeded(size)); Exception inner = new QuotaExceededException(errorMessage, socketException); result = new ProtocolException(errorMessage, inner); break; case SocketError.NetworkReset: //10052 //ICMP: Time Exceeded (TTL expired) //see http://tools.ietf.org/html/rfc792 result = new CommunicationException(SR.IcmpTimeExpired(timeToLive), socketException); break; case SocketError.ConnectionReset: //10054 //ICMP: Destination Unreachable (target host/port/etc not reachable) //see http://tools.ietf.org/html/rfc792 result = new CommunicationException(SR.IcmpDestinationUnreachable, socketException); break; default: errorMessage = (direction == TransferDirection.Send ? SR.UdpSendException : SR.UdpReceiveException); result = new CommunicationException(errorMessage, socketException); break; } } Fx.Assert(result != null, "we should never return null"); return result; } void ThrowIfDisposed() { if (this.IsDisposed) { throw FxTrace.Exception.AsError(new ObjectDisposedException(this.GetType().ToString())); } } void ThrowIfNotOpen() { ThrowIfDisposed(); if (this.openCount == 0) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ObjectNotOpen)); } } class SendToAsyncResult : TypedAsyncResult { Socket socket; int offset; int size; int timeToLive; static AsyncCallback onSendToComplete = Fx.ThunkCallback(OnSendToComplete); public SendToAsyncResult(Socket socket, byte[] buffer, int offset, int size, EndPoint remoteEndPoint, int timeToLive, AsyncCallback callback, object state) : base(callback, state) { this.socket = socket; this.offset = offset; this.size = size; this.timeToLive = timeToLive; int count = 0; try { IAsyncResult socketAsyncResult = this.socket.BeginSendTo(buffer, offset, size, SocketFlags.None, remoteEndPoint, onSendToComplete, this); if (!socketAsyncResult.CompletedSynchronously) { return; } count = this.socket.EndSendTo(socketAsyncResult); } catch (SocketException socketException) { throw FxTrace.Exception.AsError(ConvertNetworkError(socketException, this.size - this.offset, TransferDirection.Send, this.timeToLive)); } this.Complete(count, true); } static void OnSendToComplete(IAsyncResult result) { if (result.CompletedSynchronously) { return; } SendToAsyncResult thisPtr = (SendToAsyncResult)result.AsyncState; Exception completionException = null; int count = 0; try { count = thisPtr.socket.EndSendTo(result); } catch (SocketException socketException) { completionException = ConvertNetworkError(socketException, thisPtr.size - thisPtr.offset, TransferDirection.Send, thisPtr.timeToLive); } catch (Exception ex) { if (Fx.IsFatal(ex)) { throw; } completionException = ex; } if (completionException != null) { thisPtr.Complete(false, completionException); } else { thisPtr.Complete(count, false); } } } class ReceiveFromAsyncResult : TypedAsyncResult> { static AsyncCallback onReceiveMessageFromCallback = Fx.ThunkCallback(new AsyncCallback(OnReceiveMessageFrom)); Socket socket; public ReceiveFromAsyncResult(Socket socket, ArraySegment buffer, EndPoint remoteEndPoint, int messageSize, int timeToLive, AsyncCallback userCallback, object userState) : base(userCallback, userState) { this.RemoteEndPoint = remoteEndPoint; this.MessageSize = messageSize; this.socket = socket; this.Buffer = buffer; this.TimeToLive = timeToLive; ArraySegment data = default(ArraySegment); try { IAsyncResult socketAsyncResult = this.socket.BeginReceiveFrom(this.Buffer.Array, this.Buffer.Offset, this.Buffer.Count, SocketFlags.None, ref remoteEndPoint, onReceiveMessageFromCallback, this); if (!socketAsyncResult.CompletedSynchronously) { return; } data = EndReceiveFrom(socketAsyncResult); } catch (SocketException socketException) { throw FxTrace.Exception.AsError(UdpSocket.ConvertNetworkError(socketException, this)); } Complete(data, true); } public EndPoint RemoteEndPoint { get; private set; } public int TimeToLive { get; private set; } //used when generating error messages for the user... internal int MessageSize { get; private set; } ArraySegment Buffer { get; set; } public static ArraySegment End(IAsyncResult result, ref EndPoint remoteEndPoint) { ArraySegment data = TypedAsyncResult>.End(result); ReceiveFromAsyncResult receiveFromResult = (ReceiveFromAsyncResult)result; remoteEndPoint = receiveFromResult.RemoteEndPoint; return data; } static void OnReceiveMessageFrom(IAsyncResult result) { if (result.CompletedSynchronously) { return; } ReceiveFromAsyncResult asyncResult = (ReceiveFromAsyncResult)result.AsyncState; Exception completionException = null; ArraySegment data = default(ArraySegment); try { data = asyncResult.EndReceiveFrom(result); } catch (SocketException socketException) { completionException = UdpSocket.ConvertNetworkError(socketException, asyncResult); } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } completionException = exception; } if (completionException != null) { asyncResult.Complete(false, completionException); } else { asyncResult.Complete(data, false); } } ArraySegment EndReceiveFrom(IAsyncResult result) { EndPoint remoteEndPoint = this.RemoteEndPoint; int count = this.socket.EndReceiveFrom(result, ref remoteEndPoint); this.RemoteEndPoint = remoteEndPoint; return new ArraySegment(this.Buffer.Array, this.Buffer.Offset, count); } } } }