386 lines
16 KiB
C#
386 lines
16 KiB
C#
|
//----------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//----------------------------------------------------------------
|
||
|
|
||
|
namespace System.ServiceModel.Channels
|
||
|
{
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Net;
|
||
|
using System.Net.NetworkInformation;
|
||
|
using System.Net.Sockets;
|
||
|
using System.Runtime;
|
||
|
|
||
|
class UdpChannelFactory<TChannel> : ChannelFactoryBase<TChannel>
|
||
|
{
|
||
|
MessageEncoderFactory messageEncoderFactory;
|
||
|
UdpTransportBindingElement udpTransportBindingElement;
|
||
|
|
||
|
internal UdpChannelFactory(UdpTransportBindingElement transportBindingElement, BindingContext context)
|
||
|
: base(context.Binding)
|
||
|
{
|
||
|
Fx.Assert(transportBindingElement != null, "transportBindingElement can't be null");
|
||
|
Fx.Assert(context != null, "binding context can't be null");
|
||
|
Fx.Assert(typeof(TChannel) == typeof(IOutputChannel) || typeof(TChannel) == typeof(IDuplexChannel), "this channel factory only supports IOutputChannel and IDuplexChannel");
|
||
|
|
||
|
this.udpTransportBindingElement = transportBindingElement;
|
||
|
|
||
|
// We should only throw this exception if the user specified realistic MaxReceivedMessageSize less than or equal to max message size over UDP.
|
||
|
// If the user specified something bigger like Long.MaxValue, we shouldn't stop them.
|
||
|
if (this.udpTransportBindingElement.MaxReceivedMessageSize <= UdpConstants.MaxMessageSizeOverIPv4 &&
|
||
|
this.udpTransportBindingElement.SocketReceiveBufferSize < this.udpTransportBindingElement.MaxReceivedMessageSize)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentOutOfRange("SocketReceiveBufferSize", this.udpTransportBindingElement.SocketReceiveBufferSize,
|
||
|
SR.Property1LessThanOrEqualToProperty2("MaxReceivedMessageSize", this.udpTransportBindingElement.MaxReceivedMessageSize,
|
||
|
"SocketReceiveBufferSize", this.udpTransportBindingElement.SocketReceiveBufferSize));
|
||
|
}
|
||
|
|
||
|
this.messageEncoderFactory = UdpUtility.GetEncoder(context);
|
||
|
|
||
|
bool retransmissionEnabled = this.udpTransportBindingElement.RetransmissionSettings.Enabled;
|
||
|
//duplicated detection doesn't apply to IOutputChannel, so don't throw if we are only sending
|
||
|
bool duplicateDetectionEnabled = this.udpTransportBindingElement.DuplicateMessageHistoryLength > 0 ? typeof(TChannel) != typeof(IOutputChannel) : false;
|
||
|
UdpUtility.ValidateDuplicateDetectionAndRetransmittionSupport(this.messageEncoderFactory, retransmissionEnabled, duplicateDetectionEnabled);
|
||
|
|
||
|
int maxBufferSize = (int)Math.Min(transportBindingElement.MaxReceivedMessageSize, UdpConstants.MaxMessageSizeOverIPv4);
|
||
|
this.BufferManager = BufferManager.CreateBufferManager(transportBindingElement.MaxBufferPoolSize, maxBufferSize);
|
||
|
|
||
|
}
|
||
|
|
||
|
BufferManager BufferManager
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
public override T GetProperty<T>()
|
||
|
{
|
||
|
T messageEncoderProperty = this.messageEncoderFactory.Encoder.GetProperty<T>();
|
||
|
if (messageEncoderProperty != null)
|
||
|
{
|
||
|
return messageEncoderProperty;
|
||
|
}
|
||
|
|
||
|
if (typeof(T) == typeof(MessageVersion))
|
||
|
{
|
||
|
return (T)(object)this.messageEncoderFactory.Encoder.MessageVersion;
|
||
|
}
|
||
|
|
||
|
return base.GetProperty<T>();
|
||
|
}
|
||
|
|
||
|
|
||
|
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
|
||
|
{
|
||
|
this.OnOpen(timeout);
|
||
|
return new CompletedAsyncResult(callback, state);
|
||
|
}
|
||
|
|
||
|
protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)
|
||
|
{
|
||
|
Fx.Assert(to != null, "To address should have been validated as non-null by ChannelFactoryBase");
|
||
|
Fx.Assert(via != null, "Via address should have been validated as non-null by ChannelFactoryBase");
|
||
|
|
||
|
if (!via.IsAbsoluteUri)
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument("via", SR.RelativeUriNotAllowed(via));
|
||
|
}
|
||
|
|
||
|
if (!via.Scheme.Equals(UdpConstants.Scheme, StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument("via", SR.UriSchemeNotSupported(via.Scheme));
|
||
|
}
|
||
|
|
||
|
if (!UdpUtility.IsSupportedHostNameType(via.HostNameType))
|
||
|
{
|
||
|
throw FxTrace.Exception.Argument("via", SR.UnsupportedUriHostNameType(via.Host, via.HostNameType));
|
||
|
}
|
||
|
|
||
|
if (via.IsDefaultPort || via.Port == 0)
|
||
|
{
|
||
|
throw FxTrace.Exception.ArgumentOutOfRange("via", via, SR.PortNumberRequiredOnVia(via));
|
||
|
}
|
||
|
|
||
|
UdpSocket[] sockets = null;
|
||
|
IPEndPoint remoteEndPoint = null;
|
||
|
TChannel channel;
|
||
|
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
bool isMulticast;
|
||
|
sockets = GetSockets(via, out remoteEndPoint, out isMulticast);
|
||
|
|
||
|
EndpointAddress localAddress = new EndpointAddress(EndpointAddress.AnonymousUri);
|
||
|
|
||
|
if (typeof(TChannel) == typeof(IDuplexChannel))
|
||
|
{
|
||
|
UdpChannelFactory<IDuplexChannel> duplexChannelFactory = (UdpChannelFactory<IDuplexChannel>)(object)this;
|
||
|
channel = (TChannel)(object)new ClientUdpDuplexChannel(duplexChannelFactory, sockets, remoteEndPoint, localAddress, to, via, isMulticast);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UdpChannelFactory<IOutputChannel> outputChannelFactory = (UdpChannelFactory<IOutputChannel>)(object)this;
|
||
|
channel = (TChannel)(object)new ClientUdpOutputChannel(
|
||
|
outputChannelFactory,
|
||
|
remoteEndPoint,
|
||
|
outputChannelFactory.messageEncoderFactory.Encoder,
|
||
|
this.BufferManager,
|
||
|
sockets,
|
||
|
outputChannelFactory.udpTransportBindingElement.RetransmissionSettings,
|
||
|
to,
|
||
|
via,
|
||
|
isMulticast);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return channel;
|
||
|
}
|
||
|
|
||
|
protected override void OnEndOpen(IAsyncResult result)
|
||
|
{
|
||
|
CompletedAsyncResult.End(result);
|
||
|
}
|
||
|
|
||
|
protected override void OnOpen(TimeSpan timeout)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
//will only return > 1 socket when both of the following are true:
|
||
|
// 1) multicast
|
||
|
// 2) sending on all interfaces
|
||
|
UdpSocket[] GetSockets(Uri via, out IPEndPoint remoteEndPoint, out bool isMulticast)
|
||
|
{
|
||
|
UdpSocket[] results = null;
|
||
|
|
||
|
remoteEndPoint = null;
|
||
|
IPAddress[] remoteAddressList;
|
||
|
isMulticast = false;
|
||
|
|
||
|
UdpUtility.ThrowIfNoSocketSupport();
|
||
|
|
||
|
if (via.HostNameType == UriHostNameType.IPv6 || via.HostNameType == UriHostNameType.IPv4)
|
||
|
{
|
||
|
UdpUtility.ThrowOnUnsupportedHostNameType(via);
|
||
|
|
||
|
IPAddress address = IPAddress.Parse(via.DnsSafeHost);
|
||
|
isMulticast = UdpUtility.IsMulticastAddress(address);
|
||
|
|
||
|
remoteAddressList = new IPAddress[] { address };
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
remoteAddressList = DnsCache.Resolve(via).AddressList;
|
||
|
}
|
||
|
|
||
|
if (remoteAddressList.Length < 1)
|
||
|
{
|
||
|
// System.Net.Dns shouldn't ever allow this to happen, but...
|
||
|
Fx.Assert("DnsCache returned a HostEntry with zero length address list");
|
||
|
throw FxTrace.Exception.AsError(new EndpointNotFoundException(SR.DnsResolveFailed(via.DnsSafeHost)));
|
||
|
}
|
||
|
|
||
|
remoteEndPoint = new IPEndPoint(remoteAddressList[0], via.Port);
|
||
|
|
||
|
IPAddress localAddress;
|
||
|
if (via.IsLoopback)
|
||
|
{
|
||
|
localAddress = (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Loopback : IPAddress.IPv6Loopback);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
localAddress = (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any);
|
||
|
}
|
||
|
|
||
|
int port = 0;
|
||
|
|
||
|
if (isMulticast)
|
||
|
{
|
||
|
List<UdpSocket> socketList = new List<UdpSocket>();
|
||
|
NetworkInterface[] adapters = UdpUtility.GetMulticastInterfaces(this.udpTransportBindingElement.MulticastInterfaceId);
|
||
|
|
||
|
//if listening on a specific adapter, don't disable multicast loopback on that adapter.
|
||
|
bool allowMulticastLoopback = !string.IsNullOrEmpty(this.udpTransportBindingElement.MulticastInterfaceId);
|
||
|
|
||
|
for (int i = 0; i < adapters.Length; i++)
|
||
|
{
|
||
|
if (adapters[i].OperationalStatus == OperationalStatus.Up)
|
||
|
{
|
||
|
IPInterfaceProperties properties = adapters[i].GetIPProperties();
|
||
|
bool isLoopbackAdapter = adapters[i].NetworkInterfaceType == NetworkInterfaceType.Loopback;
|
||
|
|
||
|
if (isLoopbackAdapter)
|
||
|
{
|
||
|
int interfaceIndex;
|
||
|
if (UdpUtility.TryGetLoopbackInterfaceIndex(adapters[i], localAddress.AddressFamily == AddressFamily.InterNetwork, out interfaceIndex))
|
||
|
{
|
||
|
socketList.Add(UdpUtility.CreateListenSocket(localAddress, ref port, this.udpTransportBindingElement.SocketReceiveBufferSize, this.udpTransportBindingElement.TimeToLive,
|
||
|
interfaceIndex, allowMulticastLoopback, isLoopbackAdapter));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else if (localAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||
|
{
|
||
|
if (adapters[i].Supports(NetworkInterfaceComponent.IPv6))
|
||
|
{
|
||
|
IPv6InterfaceProperties v6Properties = properties.GetIPv6Properties();
|
||
|
|
||
|
if (v6Properties != null)
|
||
|
{
|
||
|
socketList.Add(UdpUtility.CreateListenSocket(localAddress, ref port, this.udpTransportBindingElement.SocketReceiveBufferSize,
|
||
|
this.udpTransportBindingElement.TimeToLive, v6Properties.Index, allowMulticastLoopback, isLoopbackAdapter));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (adapters[i].Supports(NetworkInterfaceComponent.IPv4))
|
||
|
{
|
||
|
IPv4InterfaceProperties v4Properties = properties.GetIPv4Properties();
|
||
|
if (v4Properties != null)
|
||
|
{
|
||
|
socketList.Add(UdpUtility.CreateListenSocket(localAddress, ref port, this.udpTransportBindingElement.SocketReceiveBufferSize,
|
||
|
this.udpTransportBindingElement.TimeToLive, v4Properties.Index, allowMulticastLoopback, isLoopbackAdapter));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//CreateListenSocket sets the port, but since we aren't listening
|
||
|
//on multicast, each socket can't share the same port.
|
||
|
port = 0;
|
||
|
}
|
||
|
|
||
|
if (socketList.Count == 0)
|
||
|
{
|
||
|
throw FxTrace.Exception.AsError(new ArgumentException(SR.UdpFailedToFindMulticastAdapter(via)));
|
||
|
}
|
||
|
|
||
|
results = socketList.ToArray();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UdpSocket socket = UdpUtility.CreateUnicastListenSocket(localAddress, ref port, this.udpTransportBindingElement.SocketReceiveBufferSize,
|
||
|
this.udpTransportBindingElement.TimeToLive);
|
||
|
|
||
|
results = new UdpSocket[] { socket };
|
||
|
}
|
||
|
|
||
|
|
||
|
Fx.Assert(results != null, "GetSockets(...) return results should never be null. An exception should have been thrown but wasn't.");
|
||
|
return results;
|
||
|
|
||
|
}
|
||
|
|
||
|
sealed class ClientUdpDuplexChannel : UdpDuplexChannel
|
||
|
{
|
||
|
EndpointAddress to;
|
||
|
ChannelParameterCollection channelParameters;
|
||
|
|
||
|
internal ClientUdpDuplexChannel(UdpChannelFactory<IDuplexChannel> factory, UdpSocket[] sockets, IPEndPoint remoteEndPoint, EndpointAddress localAddress, EndpointAddress to, Uri via, bool isMulticast)
|
||
|
: base(factory,
|
||
|
factory.messageEncoderFactory.Encoder,
|
||
|
factory.BufferManager,
|
||
|
sockets,
|
||
|
factory.udpTransportBindingElement.RetransmissionSettings,
|
||
|
factory.udpTransportBindingElement.MaxPendingMessagesTotalSize,
|
||
|
localAddress,
|
||
|
via,
|
||
|
isMulticast,
|
||
|
(int)factory.udpTransportBindingElement.MaxReceivedMessageSize)
|
||
|
{
|
||
|
Fx.Assert(to != null, "to address can't be null for this constructor...");
|
||
|
Fx.Assert(remoteEndPoint != null, "remoteEndPoint can't be null");
|
||
|
|
||
|
this.RemoteEndPoint = remoteEndPoint;
|
||
|
this.to = to;
|
||
|
|
||
|
if (factory.udpTransportBindingElement.DuplicateMessageHistoryLength > 0)
|
||
|
{
|
||
|
this.DuplicateDetector = new DuplicateMessageDetector(factory.udpTransportBindingElement.DuplicateMessageHistoryLength);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.DuplicateDetector = null;
|
||
|
}
|
||
|
|
||
|
UdpOutputChannel udpOutputChannel = new ClientUdpOutputChannel(factory, remoteEndPoint, factory.messageEncoderFactory.Encoder, factory.BufferManager, sockets, factory.udpTransportBindingElement.RetransmissionSettings, to, via, isMulticast);
|
||
|
this.SetOutputChannel(udpOutputChannel);
|
||
|
}
|
||
|
|
||
|
protected override bool IgnoreSerializationException
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.IsMulticast;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override EndpointAddress RemoteAddress
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.to;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IPEndPoint RemoteEndPoint
|
||
|
{
|
||
|
get;
|
||
|
private set;
|
||
|
}
|
||
|
|
||
|
public override T GetProperty<T>()
|
||
|
{
|
||
|
if (typeof(T) == typeof(ChannelParameterCollection))
|
||
|
{
|
||
|
if (this.State == CommunicationState.Created)
|
||
|
{
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.channelParameters == null)
|
||
|
{
|
||
|
this.channelParameters = new ChannelParameterCollection();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return (T)(object)this.channelParameters;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return base.GetProperty<T>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnOpened()
|
||
|
{
|
||
|
this.ReceiveManager = new UdpSocketReceiveManager(this.Sockets,
|
||
|
UdpConstants.PendingReceiveCountPerProcessor * Environment.ProcessorCount,
|
||
|
base.BufferManager,
|
||
|
this);
|
||
|
|
||
|
//do the state change to CommunicationState.Opened before starting the receive loop.
|
||
|
//this avoids a ---- between transitioning state and processing messages that are
|
||
|
//already in the socket receive buffer.
|
||
|
base.OnOpened();
|
||
|
|
||
|
this.ReceiveManager.Open();
|
||
|
}
|
||
|
|
||
|
protected override void AddHeadersTo(Message message)
|
||
|
{
|
||
|
Fx.Assert(message != null, "Message can't be null");
|
||
|
|
||
|
if (message.Version.Addressing != AddressingVersion.None)
|
||
|
{
|
||
|
this.to.ApplyTo(message);
|
||
|
}
|
||
|
|
||
|
message.Properties.Via = this.Via;
|
||
|
|
||
|
base.AddHeadersTo(message);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|