| // Copyright 2019 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "net/socket/socks_connect_job.h" | 
 |  | 
 | #include "base/callback.h" | 
 | #include "base/containers/flat_set.h" | 
 | #include "base/containers/span.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/test/task_environment.h" | 
 | #include "base/time/time.h" | 
 | #include "build/build_config.h" | 
 | #include "net/base/load_states.h" | 
 | #include "net/base/load_timing_info.h" | 
 | #include "net/base/load_timing_info_test_util.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/base/network_isolation_key.h" | 
 | #include "net/dns/mock_host_resolver.h" | 
 | #include "net/dns/public/secure_dns_policy.h" | 
 | #include "net/log/net_log.h" | 
 | #include "net/socket/client_socket_factory.h" | 
 | #include "net/socket/client_socket_handle.h" | 
 | #include "net/socket/connect_job_test_util.h" | 
 | #include "net/socket/socket_tag.h" | 
 | #include "net/socket/socket_test_util.h" | 
 | #include "net/socket/socks_connect_job.h" | 
 | #include "net/socket/transport_client_socket_pool_test_util.h" | 
 | #include "net/socket/transport_connect_job.h" | 
 | #include "net/test/gtest_util.h" | 
 | #include "net/test/test_with_task_environment.h" | 
 | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace net { | 
 | namespace { | 
 |  | 
 | const char kProxyHostName[] = "proxy.test"; | 
 | const int kProxyPort = 4321; | 
 |  | 
 | constexpr base::TimeDelta kTinyTime = base::Microseconds(1); | 
 |  | 
 | class SOCKSConnectJobTest : public testing::Test, public WithTaskEnvironment { | 
 |  public: | 
 |   enum class SOCKSVersion { | 
 |     V4, | 
 |     V5, | 
 |   }; | 
 |  | 
 |   SOCKSConnectJobTest() | 
 |       : WithTaskEnvironment(base::test::TaskEnvironment::TimeSource::MOCK_TIME), | 
 |         common_connect_job_params_( | 
 |             &client_socket_factory_, | 
 |             &host_resolver_, | 
 |             nullptr /* http_auth_cache */, | 
 |             nullptr /* http_auth_handler_factory */, | 
 |             nullptr /* spdy_session_pool */, | 
 |             nullptr /* quic_supported_versions */, | 
 |             nullptr /* quic_stream_factory */, | 
 |             nullptr /* proxy_delegate */, | 
 |             nullptr /* http_user_agent_settings */, | 
 |             nullptr /* ssl_client_context */, | 
 |             nullptr /* socket_performance_watcher_factory */, | 
 |             nullptr /* network_quality_estimator */, | 
 |             NetLog::Get(), | 
 |             nullptr /* websocket_endpoint_lock_manager */) {} | 
 |  | 
 |   ~SOCKSConnectJobTest() override = default; | 
 |  | 
 |   static scoped_refptr<SOCKSSocketParams> CreateSOCKSParams( | 
 |       SOCKSVersion socks_version, | 
 |       SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) { | 
 |     return base::MakeRefCounted<SOCKSSocketParams>( | 
 |         base::MakeRefCounted<TransportSocketParams>( | 
 |             HostPortPair(kProxyHostName, kProxyPort), NetworkAnonymizationKey(), | 
 |             secure_dns_policy, OnHostResolutionCallback(), | 
 |             /*supported_alpns=*/base::flat_set<std::string>()), | 
 |         socks_version == SOCKSVersion::V5, | 
 |         socks_version == SOCKSVersion::V4 | 
 |             ? HostPortPair(kSOCKS4TestHost, kSOCKS4TestPort) | 
 |             : HostPortPair(kSOCKS5TestHost, kSOCKS5TestPort), | 
 |         NetworkAnonymizationKey(), TRAFFIC_ANNOTATION_FOR_TESTS); | 
 |   } | 
 |  | 
 |  protected: | 
 |   MockHostResolver host_resolver_{/*default_result=*/MockHostResolverBase:: | 
 |                                       RuleResolver::GetLocalhostResult()}; | 
 |   MockTaggingClientSocketFactory client_socket_factory_; | 
 |   const CommonConnectJobParams common_connect_job_params_; | 
 | }; | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, HostResolutionFailure) { | 
 |   host_resolver_.rules()->AddSimulatedTimeoutFailure(kProxyHostName); | 
 |  | 
 |   for (bool failure_synchronous : {false, true}) { | 
 |     host_resolver_.set_synchronous_mode(failure_synchronous); | 
 |     TestConnectJobDelegate test_delegate; | 
 |     SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                       &common_connect_job_params_, | 
 |                                       CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                       &test_delegate, nullptr /* net_log */); | 
 |     test_delegate.StartJobExpectingResult( | 
 |         &socks_connect_job, ERR_PROXY_CONNECTION_FAILED, failure_synchronous); | 
 |     EXPECT_THAT(socks_connect_job.GetResolveErrorInfo().error, | 
 |                 test::IsError(ERR_DNS_TIMED_OUT)); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, HostResolutionFailureSOCKS4Endpoint) { | 
 |   const char hostname[] = "google.com"; | 
 |   host_resolver_.rules()->AddSimulatedTimeoutFailure(hostname); | 
 |  | 
 |   for (bool failure_synchronous : {false, true}) { | 
 |     host_resolver_.set_synchronous_mode(failure_synchronous); | 
 |  | 
 |     SequencedSocketData sequenced_socket_data{base::span<MockRead>(), | 
 |                                               base::span<MockWrite>()}; | 
 |     sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |     client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |     scoped_refptr<SOCKSSocketParams> socket_params = | 
 |         base::MakeRefCounted<SOCKSSocketParams>( | 
 |             base::MakeRefCounted<TransportSocketParams>( | 
 |                 HostPortPair(kProxyHostName, kProxyPort), | 
 |                 NetworkAnonymizationKey(), SecureDnsPolicy::kAllow, | 
 |                 OnHostResolutionCallback(), | 
 |                 /*supported_alpns=*/base::flat_set<std::string>()), | 
 |             false /* socks_v5 */, HostPortPair(hostname, kSOCKS4TestPort), | 
 |             NetworkAnonymizationKey(), TRAFFIC_ANNOTATION_FOR_TESTS); | 
 |  | 
 |     TestConnectJobDelegate test_delegate; | 
 |     SOCKSConnectJob socks_connect_job( | 
 |         DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_, | 
 |         socket_params, &test_delegate, nullptr /* net_log */); | 
 |     test_delegate.StartJobExpectingResult( | 
 |         &socks_connect_job, ERR_NAME_NOT_RESOLVED, failure_synchronous); | 
 |     EXPECT_THAT(socks_connect_job.GetResolveErrorInfo().error, | 
 |                 test::IsError(ERR_DNS_TIMED_OUT)); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, HandshakeError) { | 
 |   for (bool host_resolution_synchronous : {false, true}) { | 
 |     for (bool write_failure_synchronous : {false, true}) { | 
 |       host_resolver_.set_synchronous_mode(host_resolution_synchronous); | 
 |  | 
 |       // No need to distinguish which part of the handshake fails. Those details | 
 |       // are all handled at the StreamSocket layer, not the SOCKSConnectJob. | 
 |       MockWrite writes[] = { | 
 |           MockWrite(write_failure_synchronous ? SYNCHRONOUS : ASYNC, | 
 |                     ERR_UNEXPECTED, 0), | 
 |       }; | 
 |       SequencedSocketData sequenced_socket_data(base::span<MockRead>(), writes); | 
 |       // Host resolution is used to switch between sync and async connection | 
 |       // behavior. The SOCKS layer can't distinguish between sync and async host | 
 |       // resolution vs sync and async connection establishment, so just always | 
 |       // make connection establishment synchroonous. | 
 |       sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |       TestConnectJobDelegate test_delegate; | 
 |       SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |       test_delegate.StartJobExpectingResult( | 
 |           &socks_connect_job, ERR_UNEXPECTED, | 
 |           host_resolution_synchronous && write_failure_synchronous); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, SOCKS4) { | 
 |   for (bool host_resolution_synchronous : {false, true}) { | 
 |     for (bool read_and_writes_synchronous : {true}) { | 
 |       host_resolver_.set_synchronous_mode(host_resolution_synchronous); | 
 |  | 
 |       MockWrite writes[] = { | 
 |           MockWrite(SYNCHRONOUS, kSOCKS4OkRequestLocalHostPort80, | 
 |                     kSOCKS4OkRequestLocalHostPort80Length, 0), | 
 |       }; | 
 |  | 
 |       MockRead reads[] = { | 
 |           MockRead(SYNCHRONOUS, kSOCKS4OkReply, kSOCKS4OkReplyLength, 1), | 
 |       }; | 
 |  | 
 |       SequencedSocketData sequenced_socket_data(reads, writes); | 
 |       // Host resolution is used to switch between sync and async connection | 
 |       // behavior. The SOCKS layer can't distinguish between sync and async host | 
 |       // resolution vs sync and async connection establishment, so just always | 
 |       // make connection establishment synchroonous. | 
 |       sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |       TestConnectJobDelegate test_delegate; | 
 |       SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V4), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |       test_delegate.StartJobExpectingResult( | 
 |           &socks_connect_job, OK, | 
 |           host_resolution_synchronous && read_and_writes_synchronous); | 
 |  | 
 |       // Proxies should not set any DNS aliases. | 
 |       EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, SOCKS5) { | 
 |   for (bool host_resolution_synchronous : {false, true}) { | 
 |     for (bool read_and_writes_synchronous : {true}) { | 
 |       host_resolver_.set_synchronous_mode(host_resolution_synchronous); | 
 |  | 
 |       MockWrite writes[] = { | 
 |           MockWrite(SYNCHRONOUS, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength, | 
 |                     0), | 
 |           MockWrite(SYNCHRONOUS, kSOCKS5OkRequest, kSOCKS5OkRequestLength, 2), | 
 |       }; | 
 |  | 
 |       MockRead reads[] = { | 
 |           MockRead(SYNCHRONOUS, kSOCKS5GreetResponse, | 
 |                    kSOCKS5GreetResponseLength, 1), | 
 |           MockRead(SYNCHRONOUS, kSOCKS5OkResponse, kSOCKS5OkResponseLength, 3), | 
 |       }; | 
 |  | 
 |       SequencedSocketData sequenced_socket_data(reads, writes); | 
 |       // Host resolution is used to switch between sync and async connection | 
 |       // behavior. The SOCKS layer can't distinguish between sync and async host | 
 |       // resolution vs sync and async connection establishment, so just always | 
 |       // make connection establishment synchroonous. | 
 |       sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |       client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |       TestConnectJobDelegate test_delegate; | 
 |       SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |       test_delegate.StartJobExpectingResult( | 
 |           &socks_connect_job, OK, | 
 |           host_resolution_synchronous && read_and_writes_synchronous); | 
 |  | 
 |       // Proxies should not set any DNS aliases. | 
 |       EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty()); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, HasEstablishedConnection) { | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |   MockWrite writes[] = { | 
 |       MockWrite(ASYNC, kSOCKS4OkRequestLocalHostPort80, | 
 |                 kSOCKS4OkRequestLocalHostPort80Length, 0), | 
 |   }; | 
 |  | 
 |   MockRead reads[] = { | 
 |       MockRead(ASYNC, ERR_IO_PENDING, 1), | 
 |       MockRead(ASYNC, kSOCKS4OkReply, kSOCKS4OkReplyLength, 2), | 
 |   }; | 
 |  | 
 |   SequencedSocketData sequenced_socket_data(reads, writes); | 
 |   sequenced_socket_data.set_connect_data(MockConnect(ASYNC, OK)); | 
 |   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                     &common_connect_job_params_, | 
 |                                     CreateSOCKSParams(SOCKSVersion::V4), | 
 |                                     &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job.Connect(); | 
 |   EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, socks_connect_job.GetLoadState()); | 
 |   EXPECT_FALSE(socks_connect_job.HasEstablishedConnection()); | 
 |  | 
 |   host_resolver_.ResolveNow(1); | 
 |   EXPECT_EQ(LOAD_STATE_CONNECTING, socks_connect_job.GetLoadState()); | 
 |   EXPECT_FALSE(socks_connect_job.HasEstablishedConnection()); | 
 |  | 
 |   sequenced_socket_data.RunUntilPaused(); | 
 |   // "LOAD_STATE_CONNECTING" is also returned when negotiating a SOCKS | 
 |   // connection. | 
 |   EXPECT_EQ(LOAD_STATE_CONNECTING, socks_connect_job.GetLoadState()); | 
 |   EXPECT_TRUE(socks_connect_job.HasEstablishedConnection()); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |  | 
 |   sequenced_socket_data.Resume(); | 
 |   EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); | 
 |   EXPECT_TRUE(test_delegate.has_result()); | 
 | } | 
 |  | 
 | // Check that TransportConnectJob's timeout is respected for the nested | 
 | // TransportConnectJob. | 
 | TEST_F(SOCKSConnectJobTest, TimeoutDuringDnsResolution) { | 
 |   // Set HostResolver to hang. | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                     &common_connect_job_params_, | 
 |                                     CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                     &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job.Connect(); | 
 |  | 
 |   // Just before the TransportConnectJob's timeout, nothing should have | 
 |   // happened. | 
 |   FastForwardBy(TransportConnectJob::ConnectionTimeout() - kTinyTime); | 
 |   EXPECT_TRUE(host_resolver_.has_pending_requests()); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |  | 
 |   // Wait for exactly the TransportConnectJob's timeout to have passed. The Job | 
 |   // should time out. | 
 |   FastForwardBy(kTinyTime); | 
 |   EXPECT_TRUE(test_delegate.has_result()); | 
 |   EXPECT_THAT(test_delegate.WaitForResult(), | 
 |               test::IsError(ERR_PROXY_CONNECTION_FAILED)); | 
 | } | 
 |  | 
 | // Check that SOCKSConnectJob's timeout is respected for the handshake phase. | 
 | TEST_F(SOCKSConnectJobTest, TimeoutDuringHandshake) { | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |  | 
 |   MockWrite writes[] = { | 
 |       MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0), | 
 |   }; | 
 |  | 
 |   SequencedSocketData sequenced_socket_data(base::span<MockRead>(), writes); | 
 |   sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                     &common_connect_job_params_, | 
 |                                     CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                     &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job.Connect(); | 
 |  | 
 |   // Just before the TransportConnectJob's timeout, nothing should have | 
 |   // happened. | 
 |   FastForwardBy(TransportConnectJob::ConnectionTimeout() - kTinyTime); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |   EXPECT_TRUE(host_resolver_.has_pending_requests()); | 
 |  | 
 |   // DNS resolution completes, and the socket connects.  The request should not | 
 |   // time out, even after the TransportConnectJob's timeout passes. The | 
 |   // SOCKSConnectJob's handshake timer should also be started. | 
 |   host_resolver_.ResolveAllPending(); | 
 |  | 
 |   // Waiting until just before the SOCKS handshake times out. There should cause | 
 |   // no observable change in the SOCKSConnectJob's status. | 
 |   FastForwardBy(SOCKSConnectJob::HandshakeTimeoutForTesting() - kTinyTime); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |  | 
 |   // Wait for exactly the SOCKSConnectJob's handshake timeout has fully elapsed. | 
 |   // The Job should time out. | 
 |   FastForwardBy(kTinyTime); | 
 |   EXPECT_FALSE(host_resolver_.has_pending_requests()); | 
 |   EXPECT_TRUE(test_delegate.has_result()); | 
 |   EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); | 
 | } | 
 |  | 
 | // Check initial priority is passed to the HostResolver, and priority can be | 
 | // modified. | 
 | TEST_F(SOCKSConnectJobTest, Priority) { | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |   for (int initial_priority = MINIMUM_PRIORITY; | 
 |        initial_priority <= MAXIMUM_PRIORITY; ++initial_priority) { | 
 |     for (int new_priority = MINIMUM_PRIORITY; new_priority <= MAXIMUM_PRIORITY; | 
 |          ++new_priority) { | 
 |       // Don't try changing priority to itself, as APIs may not allow that. | 
 |       if (new_priority == initial_priority) | 
 |         continue; | 
 |       TestConnectJobDelegate test_delegate; | 
 |       SOCKSConnectJob socks_connect_job( | 
 |           static_cast<RequestPriority>(initial_priority), SocketTag(), | 
 |           &common_connect_job_params_, CreateSOCKSParams(SOCKSVersion::V4), | 
 |           &test_delegate, nullptr /* net_log */); | 
 |       ASSERT_THAT(socks_connect_job.Connect(), test::IsError(ERR_IO_PENDING)); | 
 |       ASSERT_TRUE(host_resolver_.has_pending_requests()); | 
 |       int request_id = host_resolver_.num_resolve(); | 
 |       EXPECT_EQ(initial_priority, host_resolver_.request_priority(request_id)); | 
 |  | 
 |       // Change priority. | 
 |       socks_connect_job.ChangePriority( | 
 |           static_cast<RequestPriority>(new_priority)); | 
 |       EXPECT_EQ(new_priority, host_resolver_.request_priority(request_id)); | 
 |  | 
 |       // Restore initial priority. | 
 |       socks_connect_job.ChangePriority( | 
 |           static_cast<RequestPriority>(initial_priority)); | 
 |       EXPECT_EQ(initial_priority, host_resolver_.request_priority(request_id)); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, SecureDnsPolicy) { | 
 |   for (auto secure_dns_policy : | 
 |        {SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}) { | 
 |     TestConnectJobDelegate test_delegate; | 
 |     SOCKSConnectJob socks_connect_job( | 
 |         DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_, | 
 |         CreateSOCKSParams(SOCKSVersion::V4, secure_dns_policy), &test_delegate, | 
 |         nullptr /* net_log */); | 
 |     ASSERT_THAT(socks_connect_job.Connect(), test::IsError(ERR_IO_PENDING)); | 
 |     EXPECT_EQ(secure_dns_policy, host_resolver_.last_secure_dns_policy()); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, ConnectTiming) { | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |  | 
 |   MockWrite writes[] = { | 
 |       MockWrite(ASYNC, ERR_IO_PENDING, 0), | 
 |       MockWrite(ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength, 1), | 
 |       MockWrite(SYNCHRONOUS, kSOCKS5OkRequest, kSOCKS5OkRequestLength, 3), | 
 |   }; | 
 |  | 
 |   MockRead reads[] = { | 
 |       MockRead(SYNCHRONOUS, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength, | 
 |                2), | 
 |       MockRead(SYNCHRONOUS, kSOCKS5OkResponse, kSOCKS5OkResponseLength, 4), | 
 |   }; | 
 |  | 
 |   SequencedSocketData sequenced_socket_data(reads, writes); | 
 |   // Host resolution is used to switch between sync and async connection | 
 |   // behavior. The SOCKS layer can't distinguish between sync and async host | 
 |   // resolution vs sync and async connection establishment, so just always | 
 |   // make connection establishment synchroonous. | 
 |   sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   SOCKSConnectJob socks_connect_job(DEFAULT_PRIORITY, SocketTag(), | 
 |                                     &common_connect_job_params_, | 
 |                                     CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                     &test_delegate, nullptr /* net_log */); | 
 |   base::TimeTicks start = base::TimeTicks::Now(); | 
 |   socks_connect_job.Connect(); | 
 |  | 
 |   // DNS resolution completes after a short delay. The connection should be | 
 |   // immediately established as well. The first write to the socket stalls. | 
 |   FastForwardBy(kTinyTime); | 
 |   host_resolver_.ResolveAllPending(); | 
 |   RunUntilIdle(); | 
 |  | 
 |   // After another short delay, data is received from the server. | 
 |   FastForwardBy(kTinyTime); | 
 |   sequenced_socket_data.Resume(); | 
 |  | 
 |   EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); | 
 |   // Proxy name resolution is not considered resolving the host name for | 
 |   // ConnectionInfo. For SOCKS4, where the host name is also looked up via DNS, | 
 |   // the resolution time is not currently reported. | 
 |   EXPECT_EQ(base::TimeTicks(), | 
 |             socks_connect_job.connect_timing().domain_lookup_start); | 
 |   EXPECT_EQ(base::TimeTicks(), | 
 |             socks_connect_job.connect_timing().domain_lookup_end); | 
 |  | 
 |   // The "connect" time for socks proxies includes DNS resolution time. | 
 |   EXPECT_EQ(start, socks_connect_job.connect_timing().connect_start); | 
 |   EXPECT_EQ(start + 2 * kTinyTime, | 
 |             socks_connect_job.connect_timing().connect_end); | 
 |  | 
 |   // Since SSL was not negotiated, SSL times are null. | 
 |   EXPECT_EQ(base::TimeTicks(), socks_connect_job.connect_timing().ssl_start); | 
 |   EXPECT_EQ(base::TimeTicks(), socks_connect_job.connect_timing().ssl_end); | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, CancelDuringDnsResolution) { | 
 |   // Set HostResolver to hang. | 
 |   host_resolver_.set_ondemand_mode(true); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   std::unique_ptr<SOCKSConnectJob> socks_connect_job = | 
 |       std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job->Connect(); | 
 |  | 
 |   EXPECT_TRUE(host_resolver_.has_pending_requests()); | 
 |  | 
 |   socks_connect_job.reset(); | 
 |   RunUntilIdle(); | 
 |   EXPECT_FALSE(host_resolver_.has_pending_requests()); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, CancelDuringConnect) { | 
 |   host_resolver_.set_synchronous_mode(true); | 
 |  | 
 |   SequencedSocketData sequenced_socket_data{base::span<MockRead>(), | 
 |                                             base::span<MockWrite>()}; | 
 |   sequenced_socket_data.set_connect_data(MockConnect(ASYNC, OK)); | 
 |   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   std::unique_ptr<SOCKSConnectJob> socks_connect_job = | 
 |       std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job->Connect(); | 
 |   // Host resolution should resolve immediately. The ConnectJob should currently | 
 |   // be trying to connect. | 
 |   EXPECT_FALSE(host_resolver_.has_pending_requests()); | 
 |  | 
 |   socks_connect_job.reset(); | 
 |   RunUntilIdle(); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |   // Socket should have been destroyed. | 
 |   EXPECT_FALSE(sequenced_socket_data.socket()); | 
 | } | 
 |  | 
 | TEST_F(SOCKSConnectJobTest, CancelDuringHandshake) { | 
 |   host_resolver_.set_synchronous_mode(true); | 
 |  | 
 |   // Hang at start of handshake. | 
 |   MockWrite writes[] = { | 
 |       MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0), | 
 |   }; | 
 |   SequencedSocketData sequenced_socket_data(base::span<MockRead>(), writes); | 
 |   sequenced_socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); | 
 |   client_socket_factory_.AddSocketDataProvider(&sequenced_socket_data); | 
 |  | 
 |   TestConnectJobDelegate test_delegate; | 
 |   std::unique_ptr<SOCKSConnectJob> socks_connect_job = | 
 |       std::make_unique<SOCKSConnectJob>(DEFAULT_PRIORITY, SocketTag(), | 
 |                                         &common_connect_job_params_, | 
 |                                         CreateSOCKSParams(SOCKSVersion::V5), | 
 |                                         &test_delegate, nullptr /* net_log */); | 
 |   socks_connect_job->Connect(); | 
 |   // Host resolution should resolve immediately. The socket connecting, and the | 
 |   // ConnectJob should currently be trying to send the SOCKS handshake. | 
 |   EXPECT_FALSE(host_resolver_.has_pending_requests()); | 
 |  | 
 |   socks_connect_job.reset(); | 
 |   RunUntilIdle(); | 
 |   EXPECT_FALSE(test_delegate.has_result()); | 
 |   // Socket should have been destroyed. | 
 |   EXPECT_FALSE(sequenced_socket_data.socket()); | 
 |   EXPECT_TRUE(sequenced_socket_data.AllWriteDataConsumed()); | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace net |