Exemplo n.º 1
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);
            }
        }