/// <summary> /// Remove link on remote proxy through rpc /// </summary> /// <param name="ct"></param> /// <returns></returns> private async Task UnlinkAsync(CancellationToken ct) { var request = Message.Create(_socket.LocalId, RemoteId, CloseRequest.Create()); try { var response = await _socket.Provider.ControlChannel.CallAsync(Proxy, request, TimeSpan.FromSeconds(10), ct).ConfigureAwait(false); ProxyEventSource.Log.LinkRemoved(this); if (response != null) { var errorCode = (SocketError)response.Error; if (errorCode != SocketError.Success && errorCode != SocketError.Timeout && errorCode != SocketError.Closed) { throw new SocketException(errorCode); } } } catch (Exception e) when(!(e is SocketException)) { throw SocketException.Create("Failed to close", e); } finally { request.Dispose(); } }
/// <summary> /// Receive a data packet /// </summary> /// <param name="buffer"></param> /// <param name="ct"></param> /// <returns></returns> public async override Task <ProxyAsyncResult> ReceiveAsync( ArraySegment <byte> buffer, CancellationToken ct) { Message message = null; try { message = await ReceiveBlock.ReceiveAsync(ct).ConfigureAwait(false); var data = message.Content as DataMessage; var copy = Math.Min(data.Payload.Length, buffer.Count); Buffer.BlockCopy(data.Payload, 0, buffer.Array, buffer.Offset, copy); return(new ProxyAsyncResult { Address = data.Source, Count = copy }); } catch (Exception e) { if (ReceiveBlock.Completion.IsFaulted) { e = ReceiveBlock.Completion.Exception; } throw SocketException.Create("Failed to receive", e); } finally { message?.Dispose(); } }
/// <summary> /// Receive using async state machine /// </summary> /// <param name="buffer"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <ProxyAsyncResult> ReceiveInternalAsync( ArraySegment <byte> buffer, CancellationToken ct) { var result = new ProxyAsyncResult(); while (true) { if (_lastData == null) { try { var message = await ReceiveBlock.ReceiveAsync(ct).ConfigureAwait(false); if (message.TypeId != MessageContent.Data) { message.Dispose(); continue; } _lastData = message.Content as DataMessage; message.Content = null; message.Dispose(); _offset = 0; } catch (Exception e) { if (ReceiveBlock.Completion.IsFaulted) { e = ReceiveBlock.Completion.Exception; } throw SocketException.Create("Failed to receive", e); } // Break on 0 sized packets if (_lastData == null) { break; } if (_lastData.Payload.Length == 0) { _lastData.Dispose(); _lastData = null; break; } #if PERF _transferred += _lastData.Payload.Length; Console.CursorLeft = 0; Console.CursorTop = 0; Console.WriteLine( $"{ _transferred / _transferredw.ElapsedMilliseconds} kB/sec"); #endif } result.Count = CopyBuffer(ref buffer); if (result.Count > 0) { break; } } return(result); }
/// <summary> /// Close link /// </summary> /// <param name="ct"></param> public async Task CloseAsync(CancellationToken ct) { var tasks = new Task[] { UnlinkAsync(ct), TerminateConnectionAsync(ct) }; try { // Close both ends await Task.WhenAll(tasks).ConfigureAwait(false); } catch (AggregateException ae) { if (ae.InnerExceptions.Count == tasks.Length) { // Only throw if all tasks failed. throw SocketException.Create("Exception during close", ae); } ProxyEventSource.Log.HandledExceptionAsInformation(this, ae.Flatten()); } catch (Exception e) { ProxyEventSource.Log.HandledExceptionAsInformation(this, e); } }
/// <summary> /// Connect to a target on first of bound proxies, or use ping based dynamic lookup /// </summary> /// <param name="address"></param> /// <param name="ct"></param> /// <returns></returns> public override async Task ConnectAsync(SocketAddress address, CancellationToken ct) { Info.Address = address; if (Info.Address.Family == AddressFamily.Bound) { // Unwrap proxy and connect address. If not bound, use local address to bind to. if (_boundEndpoint == null) { _boundEndpoint = address; // Unwrap bound address while (_boundEndpoint.Family == AddressFamily.Bound) { _boundEndpoint = ((BoundSocketAddress)_boundEndpoint).LocalAddress; } } Info.Address = ((BoundSocketAddress)Info.Address).RemoteAddress; } // // Get the named host from the registry if it exists - there should only be one... // This is the host we shall connect to. It can contain proxies to use as well. // var hostList = await Provider.NameService.LookupAsync( Info.Address.ToString(), NameRecordType.Host, ct).ConfigureAwait(false); Host = hostList.FirstOrDefault(); if (Host == null) { // If there is no host in the registry, create a fake host record for this address Host = new NameRecord(NameRecordType.Host, Info.Address.ToString()); } else { if (!Host.Name.Equals(Info.Address.ToString(), StringComparison.CurrentCultureIgnoreCase)) { // Translate the address to host address Info.Address = new ProxySocketAddress(Host.Name); } } // Commit all options that were set until now into info Info.Options.UnionWith(_optionCache.Select(p => Property <ulong> .Create( (uint)p.Key, p.Value))); // // Create tpl network for connect - prioritize input above errored attempts using // prioritized scheduling queue. // var retries = new CancellationTokenSource(); ct.Register(() => retries.Cancel()); var errors = new TransformBlock <DataflowMessage <INameRecord>, DataflowMessage <INameRecord> >( async(error) => { if (error.FaultCount > 0) { Host.RemoveReference(error.Arg.Address); await Provider.NameService.AddOrUpdateAsync(Host, retries.Token).ConfigureAwait(false); ProxyEventSource.Log.LinkFailure(this, error.Arg, Host, error.LastFault); } await Task.Delay((error.FaultCount + 1) * _throttleDelayMs, retries.Token).ConfigureAwait(false); return(error); }, new ExecutionDataflowBlockOptions { NameFormat = "Error (Connect) Id={1}", MaxDegreeOfParallelism = 2, // 2 parallel retries CancellationToken = retries.Token }); var linkAdapter = DataflowMessage <INameRecord> .CreateAdapter( new ExecutionDataflowBlockOptions { NameFormat = "Adapt (Connect) Id={1}", CancellationToken = ct, MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, MaxMessagesPerTask = DataflowBlockOptions.Unbounded, SingleProducerConstrained = true, EnsureOrdered = false }); var linkQuery = Provider.NameService.Read( new ExecutionDataflowBlockOptions { NameFormat = "Query (Connect) Id={1}", CancellationToken = ct, EnsureOrdered = true }); var pinger = CreatePingBlock(errors, Info.Address, new ExecutionDataflowBlockOptions { NameFormat = "Ping (Connect) Id={1}", CancellationToken = ct, MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, MaxMessagesPerTask = 1, EnsureOrdered = false }); var linker = CreateLinkBlock(errors, new ExecutionDataflowBlockOptions { NameFormat = "Link (Connect) Id={1}", CancellationToken = ct, MaxDegreeOfParallelism = 1, // Ensure one link is created at a time. MaxMessagesPerTask = DataflowBlockOptions.Unbounded, EnsureOrdered = false }); var connection = new WriteOnceBlock <IProxyLink>(l => l, new DataflowBlockOptions { NameFormat = "Final (Connect) Id={1}", MaxMessagesPerTask = 1, // Auto complete when link is created EnsureOrdered = false }); linkQuery.ConnectTo(linkAdapter); linkAdapter.ConnectTo(linker); errors.ConnectTo(pinger); pinger.ConnectTo(linker); linker.ConnectTo(connection); // // Now connect by starting the connection pipeline from query source... // if (_boundEndpoint != null) { // // User asked for specific set of proxies. Try linking with each // until we have a successful link. // await linkQuery.SendAsync(r => r.Matches(_boundEndpoint, NameRecordType.Proxy), ct).ConfigureAwait(false); } else { // // Consider all endpoints - if the host has a candidate list // use this list to directly link, and then ping remaining with // a throttle. Otherwise do a full ping. // var pingAdapter = DataflowMessage <INameRecord> .CreateAdapter( new ExecutionDataflowBlockOptions { NameFormat = "Any (Connect) Id={1}", CancellationToken = ct, MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, MaxMessagesPerTask = DataflowBlockOptions.Unbounded, SingleProducerConstrained = true, EnsureOrdered = false }); var remaining = Provider.NameService.Read( new ExecutionDataflowBlockOptions { NameFormat = "Remaining (Connect) Id={1}", CancellationToken = ct, EnsureOrdered = true }); remaining.ConnectTo(pingAdapter); if (Host.References.Any()) { // Delay ping through errors path to give references time to link... pingAdapter.ConnectTo(errors); await linkQuery.SendAsync(r => r.Matches(Host.References, NameRecordType.Proxy), ct).ConfigureAwait(false); } else { // Send directly to ping pingAdapter.ConnectTo(pinger); } await remaining.SendAsync(r => r.IsProxyForHost(Info.Address), ct) .ConfigureAwait(false); } // Wait until a connected link is received. Then cancel the remainder of the pipeline. try { _link = await connection.ReceiveAsync(ct); connection.Complete(); retries.Cancel(); Host.AddReference(_link.Proxy.Address); Host.LastActivity = _link.Proxy.LastActivity = DateTime.Now; await Provider.NameService.AddOrUpdateAsync(_link.Proxy, ct).ConfigureAwait(false); } catch (Exception e) { throw SocketException.Create("Failed to connect", e); } finally { await Provider.NameService.AddOrUpdateAsync(Host, ct).ConfigureAwait(false); } }