blob: e01e91439899bd525d6542af4fe36bb4d456fbc9 [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 Costinasf90a4c02020-06-12 22:30:51 +0200108TEST_F(ProxyConnectJobTest, TunnelFailedBadGatewayFromRemote) {
109 HttpTestServer http_test_server;
110 http_test_server.AddHttpConnectReply(
111 HttpTestServer::HttpConnectReply::kBadGateway);
112 http_test_server.Start();
113 remote_proxy_url_ = http_test_server.GetUrl();
114
115 connect_job_->Start();
116 cros_client_socket_->SendTo(kValidConnectRequest,
117 std::strlen(kValidConnectRequest));
118 brillo_loop_.RunOnce(false);
119
120 EXPECT_FALSE(forwarder_created_);
121
122 std::string expected_server_reply =
123 "HTTP/1.1 502 Error creating tunnel - Origin: local proxy\r\n\r\n";
124 std::vector<char> buf(expected_server_reply.size());
125 ASSERT_TRUE(cros_client_socket_->RecvFrom(buf.data(), buf.size()));
126 std::string actual_server_reply(buf.data(), buf.size());
127
128 EXPECT_EQ(expected_server_reply, actual_server_reply);
129}
130
Andreea Costinased3f9782020-05-20 17:09:46 +0200131TEST_F(ProxyConnectJobTest, SuccessfulConnectionAltEnding) {
Andreea Costinas054fbb52020-06-12 20:46:22 +0200132 HttpTestServer http_test_server;
133 http_test_server.AddHttpConnectReply(HttpTestServer::HttpConnectReply::kOk);
134 http_test_server.Start();
135 remote_proxy_url_ = http_test_server.GetUrl();
136
Andreea Costinased3f9782020-05-20 17:09:46 +0200137 connect_job_->Start();
Andreea Costinas054fbb52020-06-12 20:46:22 +0200138 char validConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n\n";
Andreea Costinased3f9782020-05-20 17:09:46 +0200139 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
140 brillo_loop_.RunOnce(false);
141
142 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
143 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
Andreea Costinas054fbb52020-06-12 20:46:22 +0200144 EXPECT_EQ(http_test_server.GetUrl(), connect_job_->proxy_servers_.front());
145 EXPECT_TRUE(forwarder_created_);
Andreea Costinased3f9782020-05-20 17:09:46 +0200146}
147
Andreea Costinase45d54b2020-03-10 09:21:14 +0100148TEST_F(ProxyConnectJobTest, BadHttpRequestWrongMethod) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200149 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100150 char badConnRequest[] = "GET www.example.server.com:443 HTTP/1.1\r\n\r\n";
151 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
152 brillo_loop_.RunOnce(false);
153
154 EXPECT_EQ("", connect_job_->target_url_);
155 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
156 const std::string expected_http_response =
157 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
158 std::vector<char> buf(expected_http_response.size());
159 ASSERT_TRUE(
160 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
161 std::string actual_response(buf.data(), buf.size());
162 EXPECT_EQ(expected_http_response, actual_response);
163}
164
165TEST_F(ProxyConnectJobTest, BadHttpRequestNoEmptyLine) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200166 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100167 // No empty line after http message.
168 char badConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
169 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
170 brillo_loop_.RunOnce(false);
171
172 EXPECT_EQ("", connect_job_->target_url_);
173 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
174 const std::string expected_http_response =
175 "HTTP/1.1 400 Bad Request - 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 EXPECT_EQ(expected_http_response, actual_response);
181}
182
Andreea Costinas08a5d182020-04-29 22:12:47 +0200183TEST_F(ProxyConnectJobTest, WaitClientConnectTimeout) {
184 // Add a TaskRunner where we can control time.
185 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
186 new base::TestMockTimeTaskRunner()};
187 loop_.SetTaskRunner(task_runner);
188 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
189
190 connect_job_->Start();
191
192 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
193 // Move the time ahead so that the client connection timeout callback is
194 // triggered.
195 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
196
197 const std::string expected_http_response =
198 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
199 std::vector<char> buf(expected_http_response.size());
200 ASSERT_TRUE(
201 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
202 std::string actual_response(buf.data(), buf.size());
203
204 EXPECT_EQ(expected_http_response, actual_response);
205}
206
207// Check that the client connect timeout callback is not fired if the owning
208// proxy connect job is destroyed.
209TEST_F(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled) {
210 // Add a TaskRunner where we can control time.
211 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
212 new base::TestMockTimeTaskRunner()};
213 loop_.SetTaskRunner(task_runner);
214 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
215
216 // Create a proxy connect job and start the client connect timeout counter.
217 {
218 int fds[2];
219 ASSERT_NE(-1,
220 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
221 0 /* protocol */, fds));
222 auto client_socket =
223 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
224
225 auto connect_job = std::make_unique<ProxyConnectJob>(
226 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
227 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
228 base::Unretained(this)),
229 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
230 base::Unretained(this)));
231 // Post the timeout task.
232 connect_job->Start();
233 EXPECT_TRUE(task_runner->HasPendingTask());
234 }
235 // Check that the task was canceled.
236 EXPECT_FALSE(task_runner->HasPendingTask());
237}
238
Andreea Costinase45d54b2020-03-10 09:21:14 +0100239} // namespace system_proxy