/// <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); } }