blob: 3dc4372cdcc8430cb318ef5e42b3c9f23bfebe04 [file] [log] [blame]
Andreea Costinase45d54b2020-03-10 09:21:14 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "system-proxy/proxy_connect_job.h"
6
7#include <netinet/in.h>
8#include <sys/socket.h>
9#include <sys/types.h>
10
11#include <gmock/gmock.h>
12#include <gtest/gtest.h>
13#include <utility>
14
Andreea Costinase45d54b2020-03-10 09:21:14 +010015#include <base/bind.h>
16#include <base/bind_helpers.h>
17#include <base/callback_helpers.h>
18#include <base/files/file_util.h>
19#include <base/files/scoped_file.h>
Andreea Costinas08a5d182020-04-29 22:12:47 +020020#include <base/test/test_mock_time_task_runner.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010021#include <brillo/message_loops/base_message_loop.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090022#include <chromeos/patchpanel/socket.h>
23#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010024
25#include "bindings/worker_common.pb.h"
26#include "system-proxy/protobuf_util.h"
Andreea Costinas054fbb52020-06-12 20:46:22 +020027#include "system-proxy/test_http_server.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010028
29namespace {
Andreea Costinas054fbb52020-06-12 20:46:22 +020030
31constexpr char kProxyServerUrl[] = "http://127.0.0.1:3128";
32constexpr char kValidConnectRequest[] =
33 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
Andreea Costinase45d54b2020-03-10 09:21:14 +010034} // namespace
35
36namespace system_proxy {
37
38using ::testing::_;
39using ::testing::Return;
40
41class ProxyConnectJobTest : public ::testing::Test {
42 public:
43 ProxyConnectJobTest() = default;
44 ProxyConnectJobTest(const ProxyConnectJobTest&) = delete;
45 ProxyConnectJobTest& operator=(const ProxyConnectJobTest&) = delete;
46 ~ProxyConnectJobTest() = default;
47
48 void SetUp() override {
49 int fds[2];
50 ASSERT_NE(-1,
51 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
52 0 /* protocol */, fds));
53 cros_client_socket_ =
Garrick Evans3388a032020-03-24 11:25:55 +090054 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
Andreea Costinase45d54b2020-03-10 09:21:14 +010055
56 connect_job_ = std::make_unique<ProxyConnectJob>(
Garrick Evans3388a032020-03-24 11:25:55 +090057 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinase45d54b2020-03-10 09:21:14 +010058 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
59 base::Unretained(this)),
60 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
61 base::Unretained(this)));
Andreea Costinase45d54b2020-03-10 09:21:14 +010062 }
63
64 protected:
65 void ResolveProxy(
66 const std::string& target_url,
67 base::OnceCallback<void(const std::list<std::string>&)> callback) {
Andreea Costinas054fbb52020-06-12 20:46:22 +020068 std::move(callback).Run({remote_proxy_url_});
Andreea Costinase45d54b2020-03-10 09:21:14 +010069 }
70
71 void OnConnectionSetupFinished(
Garrick Evans3388a032020-03-24 11:25:55 +090072 std::unique_ptr<patchpanel::SocketForwarder> fwd,
Andreea Costinase45d54b2020-03-10 09:21:14 +010073 ProxyConnectJob* connect_job) {
74 ASSERT_EQ(connect_job, connect_job_.get());
Andreea Costinas054fbb52020-06-12 20:46:22 +020075 if (fwd) {
76 forwarder_created_ = true;
77 }
Andreea Costinase45d54b2020-03-10 09:21:14 +010078 }
79
Andreea Costinas054fbb52020-06-12 20:46:22 +020080 std::string remote_proxy_url_ = kProxyServerUrl;
81 bool forwarder_created_ = false;
Andreea Costinase45d54b2020-03-10 09:21:14 +010082 std::unique_ptr<ProxyConnectJob> connect_job_;
83 base::MessageLoopForIO loop_;
84 brillo::BaseMessageLoop brillo_loop_{&loop_};
Garrick Evans3388a032020-03-24 11:25:55 +090085 std::unique_ptr<patchpanel::Socket> cros_client_socket_;
Andreea Costinas08a5d182020-04-29 22:12:47 +020086
87 private:
88 FRIEND_TEST(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled);
Andreea Costinase45d54b2020-03-10 09:21:14 +010089};
90
91TEST_F(ProxyConnectJobTest, SuccessfulConnection) {
Andreea Costinas054fbb52020-06-12 20:46:22 +020092 HttpTestServer http_test_server;
93 http_test_server.AddHttpConnectReply(HttpTestServer::HttpConnectReply::kOk);
94 http_test_server.Start();
95 remote_proxy_url_ = http_test_server.GetUrl();
96
Andreea Costinas08a5d182020-04-29 22:12:47 +020097 connect_job_->Start();
Andreea Costinas054fbb52020-06-12 20:46:22 +020098 cros_client_socket_->SendTo(kValidConnectRequest,
99 std::strlen(kValidConnectRequest));
Andreea Costinase45d54b2020-03-10 09:21:14 +0100100 brillo_loop_.RunOnce(false);
101
Andreea Costinasa2246592020-04-12 23:24:01 +0200102 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100103 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
Andreea Costinas054fbb52020-06-12 20:46:22 +0200104 EXPECT_EQ(http_test_server.GetUrl(), connect_job_->proxy_servers_.front());
105 EXPECT_TRUE(forwarder_created_);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100106}
107
Andreea Costinased3f9782020-05-20 17:09:46 +0200108TEST_F(ProxyConnectJobTest, SuccessfulConnectionAltEnding) {
Andreea Costinas054fbb52020-06-12 20:46:22 +0200109 HttpTestServer http_test_server;
110 http_test_server.AddHttpConnectReply(HttpTestServer::HttpConnectReply::kOk);
111 http_test_server.Start();
112 remote_proxy_url_ = http_test_server.GetUrl();
113
Andreea Costinased3f9782020-05-20 17:09:46 +0200114 connect_job_->Start();
Andreea Costinas054fbb52020-06-12 20:46:22 +0200115 char validConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n\n";
Andreea Costinased3f9782020-05-20 17:09:46 +0200116 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
117 brillo_loop_.RunOnce(false);
118
119 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
120 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
Andreea Costinas054fbb52020-06-12 20:46:22 +0200121 EXPECT_EQ(http_test_server.GetUrl(), connect_job_->proxy_servers_.front());
122 EXPECT_TRUE(forwarder_created_);
Andreea Costinased3f9782020-05-20 17:09:46 +0200123}
124
Andreea Costinase45d54b2020-03-10 09:21:14 +0100125TEST_F(ProxyConnectJobTest, BadHttpRequestWrongMethod) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200126 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100127 char badConnRequest[] = "GET www.example.server.com:443 HTTP/1.1\r\n\r\n";
128 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
129 brillo_loop_.RunOnce(false);
130
131 EXPECT_EQ("", connect_job_->target_url_);
132 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
133 const std::string expected_http_response =
134 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
135 std::vector<char> buf(expected_http_response.size());
136 ASSERT_TRUE(
137 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
138 std::string actual_response(buf.data(), buf.size());
139 EXPECT_EQ(expected_http_response, actual_response);
140}
141
142TEST_F(ProxyConnectJobTest, BadHttpRequestNoEmptyLine) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200143 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100144 // No empty line after http message.
145 char badConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
146 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
147 brillo_loop_.RunOnce(false);
148
149 EXPECT_EQ("", connect_job_->target_url_);
150 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
151 const std::string expected_http_response =
152 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
153 std::vector<char> buf(expected_http_response.size());
154 ASSERT_TRUE(
155 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
156 std::string actual_response(buf.data(), buf.size());
157 EXPECT_EQ(expected_http_response, actual_response);
158}
159
Andreea Costinas08a5d182020-04-29 22:12:47 +0200160TEST_F(ProxyConnectJobTest, WaitClientConnectTimeout) {
161 // Add a TaskRunner where we can control time.
162 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
163 new base::TestMockTimeTaskRunner()};
164 loop_.SetTaskRunner(task_runner);
165 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
166
167 connect_job_->Start();
168
169 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
170 // Move the time ahead so that the client connection timeout callback is
171 // triggered.
172 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
173
174 const std::string expected_http_response =
175 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
176 std::vector<char> buf(expected_http_response.size());
177 ASSERT_TRUE(
178 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
179 std::string actual_response(buf.data(), buf.size());
180
181 EXPECT_EQ(expected_http_response, actual_response);
182}
183
184// Check that the client connect timeout callback is not fired if the owning
185// proxy connect job is destroyed.
186TEST_F(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled) {
187 // Add a TaskRunner where we can control time.
188 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
189 new base::TestMockTimeTaskRunner()};
190 loop_.SetTaskRunner(task_runner);
191 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
192
193 // Create a proxy connect job and start the client connect timeout counter.
194 {
195 int fds[2];
196 ASSERT_NE(-1,
197 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
198 0 /* protocol */, fds));
199 auto client_socket =
200 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
201
202 auto connect_job = std::make_unique<ProxyConnectJob>(
203 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
204 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
205 base::Unretained(this)),
206 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
207 base::Unretained(this)));
208 // Post the timeout task.
209 connect_job->Start();
210 EXPECT_TRUE(task_runner->HasPendingTask());
211 }
212 // Check that the task was canceled.
213 EXPECT_FALSE(task_runner->HasPendingTask());
214}
215
Andreea Costinase45d54b2020-03-10 09:21:14 +0100216} // namespace system_proxy