/// <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();
            }
        }
Example #3
0
        /// <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);
            }
        }
Example #5
0
        /// <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);
            }
        }