479 lines
16 KiB
C#
479 lines
16 KiB
C#
|
//----------------------------------------------------------------
|
||
|
// 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<byte>(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<byte> 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<int>
|
||
|
{
|
||
|
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<ArraySegment<byte>>
|
||
|
{
|
||
|
static AsyncCallback onReceiveMessageFromCallback = Fx.ThunkCallback(new AsyncCallback(OnReceiveMessageFrom));
|
||
|
Socket socket;
|
||
|
|
||
|
public ReceiveFromAsyncResult(Socket socket, ArraySegment<byte> 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<byte> data = default(ArraySegment<byte>);
|
||
|
|
||
|
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<byte> Buffer
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
public static ArraySegment<byte> End(IAsyncResult result, ref EndPoint remoteEndPoint)
|
||
|
{
|
||
|
ArraySegment<byte> data = TypedAsyncResult<ArraySegment<byte>>.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<byte> data = default(ArraySegment<byte>);
|
||
|
|
||
|
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<byte> EndReceiveFrom(IAsyncResult result)
|
||
|
{
|
||
|
EndPoint remoteEndPoint = this.RemoteEndPoint;
|
||
|
int count = this.socket.EndReceiveFrom(result, ref remoteEndPoint);
|
||
|
this.RemoteEndPoint = remoteEndPoint;
|
||
|
return new ArraySegment<byte>(this.Buffer.Array, this.Buffer.Offset, count);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|