| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.IO; |
| | | 7 | | using System.IO.Pipelines; |
| | | 8 | | using System.Linq.Expressions; |
| | | 9 | | using System.Net; |
| | | 10 | | using System.Reflection; |
| | | 11 | | using System.Threading; |
| | | 12 | | using System.Threading.Tasks; |
| | | 13 | | using CoreWCF.Configuration; |
| | | 14 | | using CoreWCF.Runtime; |
| | | 15 | | using CoreWCF.Security; |
| | | 16 | | using Microsoft.AspNetCore.Connections; |
| | | 17 | | using Microsoft.AspNetCore.Http.Features; |
| | | 18 | | using Microsoft.Extensions.Logging; |
| | | 19 | | using Microsoft.Extensions.Logging.Abstractions; |
| | | 20 | | |
| | | 21 | | namespace CoreWCF.Channels.Framing |
| | | 22 | | { |
| | | 23 | | public class FramingConnection |
| | | 24 | | { |
| | | 25 | | private readonly ConnectionContext _context; |
| | | 26 | | |
| | 82 | 27 | | public FramingConnection(ConnectionContext context) |
| | | 28 | | { |
| | 82 | 29 | | _context = context; |
| | 82 | 30 | | Logger = context.Features.Get<ILogger>(); |
| | 82 | 31 | | if (Logger== null) |
| | | 32 | | { |
| | 3 | 33 | | Logger = NullLogger.Instance; |
| | | 34 | | } |
| | | 35 | | |
| | 82 | 36 | | Transport = RawTransport = _context.Transport; |
| | 82 | 37 | | RemoteEndpoint = GetRemoteEndPoint(context); |
| | 82 | 38 | | SetInitializationToken(true); |
| | 82 | 39 | | } |
| | | 40 | | |
| | 527 | 41 | | internal CancellationToken ChannelInitializationCancellationToken { get; private set; } |
| | 510 | 42 | | public MessageEncoderFactory MessageEncoderFactory { get; internal set; } |
| | 536 | 43 | | public StreamUpgradeAcceptor StreamUpgradeAcceptor { get; internal set; } |
| | 219 | 44 | | public ISecurityCapabilities SecurityCapabilities { get; internal set; } |
| | 1431 | 45 | | public IServiceDispatcher ServiceDispatcher { get; internal set; } |
| | 1362 | 46 | | public PipeReader Input => Transport.Input; |
| | 2014 | 47 | | public PipeWriter Output => Transport.Output; |
| | 3693 | 48 | | public IDuplexPipe Transport { get; set; } |
| | 453 | 49 | | public IDuplexPipe RawTransport { get; private set; } |
| | 885 | 50 | | internal FramingDecoder FramingDecoder { get; set; } |
| | 229 | 51 | | public Uri Via => FramingDecoder?.Via; |
| | 385 | 52 | | internal FramingMode FramingMode { get; set; } |
| | 309 | 53 | | public MessageEncoder MessageEncoder { get; internal set; } |
| | 265 | 54 | | public IFeatureCollection ConnectionFeatures => _context.Features; |
| | | 55 | | public SecurityMessageProperty SecurityMessageProperty |
| | | 56 | | { |
| | 136 | 57 | | get; |
| | 113 | 58 | | internal set; |
| | | 59 | | } |
| | 1234 | 60 | | public bool EOF { get; internal set; } |
| | 772 | 61 | | public Memory<byte> EnvelopeBuffer { get; internal set; } |
| | 485 | 62 | | public int EnvelopeOffset { get; internal set; } |
| | 462 | 63 | | public BufferManager BufferManager { get; internal set; } |
| | 333 | 64 | | public int EnvelopeSize { get; internal set; } |
| | 269 | 65 | | public long MaxReceivedMessageSize { get; internal set; } |
| | 403 | 66 | | public int MaxBufferSize { get; internal set; } |
| | 269 | 67 | | public int ConnectionBufferSize { get; internal set; } |
| | 269 | 68 | | public TransferMode TransferMode { get; internal set; } |
| | 235 | 69 | | internal Stream RawStream { get; set; } |
| | 806 | 70 | | public ILogger Logger { get; } |
| | 126 | 71 | | public IPEndPoint RemoteEndpoint { get; } |
| | | 72 | | |
| | | 73 | | internal void Reset() |
| | | 74 | | { |
| | 105 | 75 | | SetInitializationToken(false); |
| | 105 | 76 | | MessageEncoderFactory = default; |
| | 105 | 77 | | StreamUpgradeAcceptor = default; |
| | 105 | 78 | | SecurityCapabilities = default; |
| | 105 | 79 | | ServiceDispatcher = default; |
| | 105 | 80 | | Transport = RawTransport; |
| | 105 | 81 | | FramingDecoder = default; |
| | 105 | 82 | | FramingMode = default; |
| | 105 | 83 | | MessageEncoder = default; |
| | 105 | 84 | | SecurityMessageProperty = default; |
| | 105 | 85 | | EOF = default; |
| | 105 | 86 | | EnvelopeBuffer = default; |
| | 105 | 87 | | EnvelopeOffset = default; |
| | 105 | 88 | | BufferManager = default; |
| | 105 | 89 | | EnvelopeSize = default; |
| | 105 | 90 | | MaxReceivedMessageSize = default; |
| | 105 | 91 | | MaxBufferSize = default; |
| | 105 | 92 | | ConnectionBufferSize = default; |
| | 105 | 93 | | TransferMode = default; |
| | 105 | 94 | | RawStream = default; |
| | 105 | 95 | | } |
| | | 96 | | |
| | 2 | 97 | | public void Abort() { _context.Abort(new ConnectionAbortedException()); } |
| | 4 | 98 | | public void Abort(Exception e) { _context.Abort(new ConnectionAbortedException(e.Message, e)); } |
| | 14 | 99 | | public void Abort(string reason) { _context.Abort(new ConnectionAbortedException(reason)); } |
| | | 100 | | |
| | | 101 | | public Task CloseAsync(TimeSpan timeout) |
| | | 102 | | { |
| | | 103 | | // Closing should be async and should accept a timeout. There are improvements coming in future releases of |
| | 0 | 104 | | Input.Complete(); |
| | 0 | 105 | | Output.Complete(); |
| | 0 | 106 | | return Task.CompletedTask; |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | internal async Task SendFaultAsync(string faultString, int maxRead, CancellationToken cancellationToken) |
| | | 110 | | { |
| | | 111 | | //if (TD.ConnectionReaderSendFaultIsEnabled()) |
| | | 112 | | //{ |
| | | 113 | | // TD.ConnectionReaderSendFault(faultString); |
| | | 114 | | //} |
| | 3 | 115 | | var encodedFault = new EncodedFault(faultString); |
| | | 116 | | try |
| | | 117 | | { |
| | 3 | 118 | | await Output.WriteAsync(encodedFault.EncodedBytes, cancellationToken); |
| | 3 | 119 | | await Output.FlushAsync(); |
| | | 120 | | // Connection will be closed on completion of Task returned from NetMessageFramingConnectionHandler.OnCo |
| | 3 | 121 | | } |
| | 0 | 122 | | catch (CommunicationException e) // TODO: Consider exception filters to remvoe duplicate code |
| | | 123 | | { |
| | 0 | 124 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 0 | 125 | | Abort(e); |
| | 0 | 126 | | return; |
| | | 127 | | } |
| | 0 | 128 | | catch (OperationCanceledException e) |
| | | 129 | | { |
| | | 130 | | //if (TD.SendTimeoutIsEnabled()) |
| | | 131 | | //{ |
| | | 132 | | // TD.SendTimeout(e.Message); |
| | | 133 | | //} |
| | 0 | 134 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 0 | 135 | | Abort(e); |
| | 0 | 136 | | return; |
| | | 137 | | } |
| | 0 | 138 | | catch (TimeoutException e) |
| | | 139 | | { |
| | | 140 | | //if (TD.SendTimeoutIsEnabled()) |
| | | 141 | | //{ |
| | | 142 | | // TD.SendTimeout(e.Message); |
| | | 143 | | //} |
| | 0 | 144 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 0 | 145 | | Abort(e); |
| | 0 | 146 | | return; |
| | | 147 | | } |
| | | 148 | | |
| | | 149 | | // make sure we read until EOF or a quota is hit |
| | | 150 | | ReadResult readResult; |
| | 3 | 151 | | long readTotal = 0; |
| | | 152 | | for (; ; ) |
| | | 153 | | { |
| | | 154 | | try |
| | | 155 | | { |
| | 6 | 156 | | readResult = await Input.ReadAsync(cancellationToken); |
| | 4 | 157 | | } |
| | 1 | 158 | | catch (CommunicationException e) // TODO: Exception filters? |
| | | 159 | | { |
| | 1 | 160 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 1 | 161 | | Abort(e); |
| | 1 | 162 | | return; |
| | | 163 | | } |
| | | 164 | | // TODO: Standardize handling of OperationCanceledException/TimeoutException |
| | 0 | 165 | | catch (OperationCanceledException e) |
| | | 166 | | { |
| | | 167 | | //if (TD.SendTimeoutIsEnabled()) |
| | | 168 | | //{ |
| | | 169 | | // TD.SendTimeout(e.Message); |
| | | 170 | | //} |
| | 0 | 171 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 0 | 172 | | Abort(e); |
| | 0 | 173 | | return; |
| | | 174 | | } |
| | 0 | 175 | | catch (TimeoutException e) |
| | | 176 | | { |
| | | 177 | | //if (TD.SendTimeoutIsEnabled()) |
| | | 178 | | //{ |
| | | 179 | | // TD.SendTimeout(e.Message); |
| | | 180 | | //} |
| | 0 | 181 | | DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); |
| | 0 | 182 | | Abort(e); |
| | 0 | 183 | | return; |
| | | 184 | | } |
| | | 185 | | |
| | 4 | 186 | | if (readResult.IsCompleted) |
| | | 187 | | { |
| | | 188 | | break; |
| | | 189 | | } |
| | | 190 | | |
| | 3 | 191 | | readTotal += readResult.Buffer.Length; |
| | 3 | 192 | | Input.AdvanceTo(readResult.Buffer.End); |
| | 3 | 193 | | if (readTotal > maxRead || cancellationToken.IsCancellationRequested) |
| | | 194 | | { |
| | 0 | 195 | | Abort(); |
| | 1 | 196 | | return; |
| | | 197 | | } |
| | | 198 | | } |
| | 2 | 199 | | } |
| | | 200 | | |
| | | 201 | | /// <summary> |
| | | 202 | | /// Tries to extract the remote endpoint from the given <see cref="ConnectionContext"/> in a |
| | | 203 | | /// version agnostic way. |
| | | 204 | | /// </summary> |
| | | 205 | | /// <param name="context">The ASP.net core connection context to extract the remote endpoint from.</param> |
| | | 206 | | /// <returns>The endpoint of the remote party or null if it was not provided.</returns> |
| | | 207 | | private static IPEndPoint GetRemoteEndPoint(ConnectionContext context) |
| | | 208 | | { |
| | | 209 | | // 1st chance: Server might provide remote endpoint via HTTP feature |
| | | 210 | | // (mostly the case in ASP.net core v2.x) |
| | 82 | 211 | | IHttpConnectionFeature connectionFeature = context.Features.Get<IHttpConnectionFeature>(); |
| | 82 | 212 | | if (connectionFeature != null) |
| | | 213 | | { |
| | 0 | 214 | | return new IPEndPoint(connectionFeature.RemoteIpAddress, connectionFeature.RemotePort); |
| | | 215 | | } |
| | | 216 | | |
| | | 217 | | // 2nd chance: on ASP.net core 5.0 the ConnectionContext has a direct Property RemoteEndpoint |
| | | 218 | | // via baseclass. |
| | 82 | 219 | | var net5RemoteEndPointPropertyAccessor = s_net5RemoteEndPointPropertyAccessor; |
| | 82 | 220 | | if (net5RemoteEndPointPropertyAccessor != null) |
| | | 221 | | { |
| | 82 | 222 | | return net5RemoteEndPointPropertyAccessor(context); |
| | | 223 | | } |
| | | 224 | | |
| | | 225 | | // last chance: server does likely not support access to remote endpoint. could be |
| | | 226 | | // a non-tcp server like the ASP.net core test server |
| | 0 | 227 | | return null; |
| | | 228 | | } |
| | | 229 | | |
| | | 230 | | |
| | 2 | 231 | | private static readonly Func<ConnectionContext, IPEndPoint> s_net5RemoteEndPointPropertyAccessor = |
| | 2 | 232 | | BuildNet5RemoteEndPointPropertyAccessor(); |
| | | 233 | | |
| | | 234 | | private static Func<ConnectionContext, IPEndPoint> BuildNet5RemoteEndPointPropertyAccessor() |
| | | 235 | | { |
| | | 236 | | // https://github.com/dotnet/aspnetcore/blob/v5.0.9/src/Servers/Connections.Abstractions/src/BaseConnectionC |
| | 2 | 237 | | var property = |
| | 2 | 238 | | typeof(ConnectionContext).GetProperty("RemoteEndPoint", BindingFlags.Instance | BindingFlags.Public); |
| | 2 | 239 | | if (property == null) |
| | | 240 | | { |
| | 0 | 241 | | return null; |
| | | 242 | | } |
| | | 243 | | |
| | | 244 | | // context => context.RemoteEndPoint as IPEndpoint |
| | 2 | 245 | | var contextParam = Expression.Parameter(typeof(ConnectionContext), "context"); |
| | 2 | 246 | | return Expression.Lambda<Func<ConnectionContext, IPEndPoint>>( |
| | 2 | 247 | | Expression.TypeAs(Expression.Property(contextParam, property), typeof(IPEndPoint)), |
| | 2 | 248 | | contextParam |
| | 2 | 249 | | ).Compile(); |
| | | 250 | | } |
| | | 251 | | |
| | | 252 | | internal void SetInitializationToken(bool isFirstChannel) |
| | | 253 | | { |
| | | 254 | | // The first time the ChannelInitializationToken token is used, we use the ChannelInitializationTimeout. On |
| | | 255 | | // we are reusing the connection, and the IdleTimeout will get used instead. |
| | 187 | 256 | | var listenOptions = ConnectionFeatures.Get<NetFramingListenOptions>(); |
| | 187 | 257 | | TimeSpan connectionInitializationTimeout = isFirstChannel ? TimeSpan.FromSeconds(30) : TimeSpan.FromMinutes( |
| | 187 | 258 | | if (listenOptions != null) |
| | | 259 | | { |
| | 184 | 260 | | connectionInitializationTimeout = isFirstChannel ? listenOptions.ConnectionPoolSettings.ChannelInitializ |
| | | 261 | | } |
| | | 262 | | |
| | 187 | 263 | | ChannelInitializationCancellationToken = new TimeoutHelper(connectionInitializationTimeout).GetCancellationT |
| | 187 | 264 | | } |
| | | 265 | | } |
| | | 266 | | } |