blob: 66a34e740d2ced157c4933ea67a350d01f468c93 [file] [log] [blame]
Andreea Costinas44cefa22020-03-09 09:07:39 +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/server_proxy.h"
6
7#include <netinet/in.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +01008#include <sys/socket.h>
9#include <sys/types.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010010
11#include <gmock/gmock.h>
12#include <gtest/gtest.h>
13#include <utility>
14
15#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>
20#include <base/message_loop/message_loop.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010021#include <base/strings/string_util.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010022#include <brillo/dbus/async_event_sequencer.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010023#include <brillo/message_loops/base_message_loop.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090024#include <chromeos/patchpanel/socket.h>
25#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010026#include "bindings/worker_common.pb.h"
27#include "system-proxy/protobuf_util.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010028#include "system-proxy/proxy_connect_job.h"
Andreea Costinas44cefa22020-03-09 09:07:39 +010029
30namespace system_proxy {
31namespace {
Andreea Costinase45d54b2020-03-10 09:21:14 +010032constexpr char kUsername[] = "proxy:user";
33constexpr char kUsernameEncoded[] = "proxy%3Auser";
34constexpr char kPassword[] = "proxy password";
35constexpr char kPasswordEncoded[] = "proxy%20password";
Andreea Costinas44cefa22020-03-09 09:07:39 +010036constexpr int kTestPort = 3128;
Andreea Costinas5862b102020-03-19 14:45:36 +010037constexpr char kFakeProxyAddress[] = "http://127.0.0.1";
Andreea Costinase45d54b2020-03-10 09:21:14 +010038
Andreea Costinas44cefa22020-03-09 09:07:39 +010039} // namespace
40
Andreea Costinase45d54b2020-03-10 09:21:14 +010041using ::testing::_;
Andreea Costinas44cefa22020-03-09 09:07:39 +010042using ::testing::Return;
43
44class MockServerProxy : public ServerProxy {
45 public:
46 explicit MockServerProxy(base::OnceClosure quit_closure)
47 : ServerProxy(std::move(quit_closure)) {}
48 MockServerProxy(const MockServerProxy&) = delete;
49 MockServerProxy& operator=(const MockServerProxy&) = delete;
50 ~MockServerProxy() override = default;
51
52 MOCK_METHOD(int, GetStdinPipe, (), (override));
Andreea Costinas5862b102020-03-19 14:45:36 +010053 MOCK_METHOD(int, GetStdoutPipe, (), (override));
Andreea Costinas44cefa22020-03-09 09:07:39 +010054};
55
Andreea Costinase45d54b2020-03-10 09:21:14 +010056class MockProxyConnectJob : public ProxyConnectJob {
57 public:
Garrick Evans3388a032020-03-24 11:25:55 +090058 MockProxyConnectJob(std::unique_ptr<patchpanel::Socket> socket,
Andreea Costinase45d54b2020-03-10 09:21:14 +010059 const std::string& credentials,
60 ResolveProxyCallback resolve_proxy_callback,
61 OnConnectionSetupFinishedCallback setup_finished_callback)
62 : ProxyConnectJob(std::move(socket),
63 credentials,
64 std::move(resolve_proxy_callback),
65 std::move(setup_finished_callback)) {}
66 MockProxyConnectJob(const MockProxyConnectJob&) = delete;
67 MockProxyConnectJob& operator=(const MockProxyConnectJob&) = delete;
68 ~MockProxyConnectJob() override = default;
69
70 MOCK_METHOD(bool, Start, (), (override));
71};
72
Andreea Costinas44cefa22020-03-09 09:07:39 +010073class ServerProxyTest : public ::testing::Test {
74 public:
75 ServerProxyTest() {
76 server_proxy_ =
77 std::make_unique<MockServerProxy>(brillo_loop_.QuitClosure());
78 }
79
80 ServerProxyTest(const ServerProxyTest&) = delete;
81 ServerProxyTest& operator=(const ServerProxyTest&) = delete;
82 ~ServerProxyTest() override {}
83
84 protected:
Andreea Costinas5862b102020-03-19 14:45:36 +010085 // Redirects the standard streams of the worker so that the tests can write
86 // data in the worker's stdin input and read data from the worker's stdout
87 // output.
Andreea Costinas44cefa22020-03-09 09:07:39 +010088 void RedirectStdPipes() {
89 int fds[2];
90 CHECK(base::CreateLocalNonBlockingPipe(fds));
Andreea Costinas5862b102020-03-19 14:45:36 +010091 stdin_read_fd_.reset(fds[0]);
92 stdin_write_fd_.reset(fds[1]);
93 CHECK(base::CreateLocalNonBlockingPipe(fds));
94 stdout_read_fd_.reset(fds[0]);
95 stdout_write_fd_.reset(fds[1]);
Andreea Costinas44cefa22020-03-09 09:07:39 +010096
Andreea Costinas5862b102020-03-19 14:45:36 +010097 ON_CALL(*server_proxy_, GetStdinPipe())
98 .WillByDefault(Return(stdin_read_fd_.get()));
99 // Don't redirect all the calls to |stdout_write_fd_| or the test result
100 // will not be printed in the console. Instead, when wanting to read the
101 // standard output, set the expectation to once return |stdout_write_fd_|.
102 ON_CALL(*server_proxy_, GetStdoutPipe())
103 .WillByDefault(Return(STDOUT_FILENO));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100104 server_proxy_->Init();
105 }
106 // SystemProxyAdaptor instance that creates fake worker processes.
107 std::unique_ptr<MockServerProxy> server_proxy_;
108 base::MessageLoopForIO loop_;
109 brillo::BaseMessageLoop brillo_loop_{&loop_};
Andreea Costinas5862b102020-03-19 14:45:36 +0100110 base::ScopedFD stdin_read_fd_, stdin_write_fd_, stdout_read_fd_,
111 stdout_write_fd_;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100112};
113
114TEST_F(ServerProxyTest, FetchCredentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200115 worker::Credentials credentials;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100116 credentials.set_username(kUsername);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100117 credentials.set_password(kPassword);
Andreea Costinasaae97382020-05-05 13:31:58 +0200118 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100119 *configs.mutable_credentials() = credentials;
120 RedirectStdPipes();
121
Andreea Costinas5862b102020-03-19 14:45:36 +0100122 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100123
124 brillo_loop_.RunOnce(false);
125
Andreea Costinase45d54b2020-03-10 09:21:14 +0100126 std::string expected_credentials =
127 base::JoinString({kUsernameEncoded, kPasswordEncoded}, ":");
128 EXPECT_EQ(server_proxy_->credentials_, expected_credentials);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100129}
130
131TEST_F(ServerProxyTest, FetchListeningAddress) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200132 worker::SocketAddress address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100133 address.set_addr(INADDR_ANY);
134 address.set_port(kTestPort);
Andreea Costinasaae97382020-05-05 13:31:58 +0200135 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100136 *configs.mutable_listening_address() = address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100137 // Redirect the worker stdin and stdout pipes.
Andreea Costinas5862b102020-03-19 14:45:36 +0100138 RedirectStdPipes();
139 // Send the config to the worker's stdin input.
140 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100141 brillo_loop_.RunOnce(false);
142
143 EXPECT_EQ(server_proxy_->listening_addr_, INADDR_ANY);
144 EXPECT_EQ(server_proxy_->listening_port_, kTestPort);
145}
146
Andreea Costinas5862b102020-03-19 14:45:36 +0100147// Tests that ServerProxy handles the basic flow of a connect request:
148// - server accepts a connection a creates a job for it until the connection is
149// finished;
150// - the connect request from the client socket is read and parsed;
151// - proxy resolution request is correctly handled by the job and ServerProxy;
152// - client is sent an HTTP error code in case of failure;
153// - the failed connection job is removed from the queue.
Andreea Costinase45d54b2020-03-10 09:21:14 +0100154TEST_F(ServerProxyTest, HandleConnectRequest) {
155 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
156 server_proxy_->listening_port_ = kTestPort;
157 // Redirect the worker stdin and stdout pipes.
158 RedirectStdPipes();
159 server_proxy_->CreateListeningSocket();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100160 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
161 brillo_loop_.RunOnce(false);
162
163 struct sockaddr_in ipv4addr;
164 ipv4addr.sin_family = AF_INET;
165 ipv4addr.sin_port = htons(kTestPort);
166 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
167
168 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900169 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100170 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
171 sizeof(ipv4addr)));
172 brillo_loop_.RunOnce(false);
173
174 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
Andreea Costinas5862b102020-03-19 14:45:36 +0100175 const std::string_view http_req =
176 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
177 client_socket->SendTo(http_req.data(), http_req.size());
178
179 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
180 .WillOnce(Return(stdout_write_fd_.get()));
181 brillo_loop_.RunOnce(false);
Andreea Costinasaae97382020-05-05 13:31:58 +0200182 worker::WorkerRequest request;
Andreea Costinas5862b102020-03-19 14:45:36 +0100183 // Read the request from the worker's stdout output.
184 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
185 ASSERT_TRUE(request.has_proxy_resolution_request());
186
Andreea Costinasa89309d2020-05-08 15:51:12 +0200187 EXPECT_EQ("https://www.example.server.com:443",
Andreea Costinas5862b102020-03-19 14:45:36 +0100188 request.proxy_resolution_request().target_url());
189
190 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
191
192 // Write reply with a fake proxy to the worker's standard input.
Andreea Costinasaae97382020-05-05 13:31:58 +0200193 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100194 reply.set_target_url(request.proxy_resolution_request().target_url());
195 reply.add_proxy_servers(kFakeProxyAddress);
Andreea Costinasaae97382020-05-05 13:31:58 +0200196 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100197 *configs.mutable_proxy_resolution_reply() = reply;
198
199 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
200 brillo_loop_.RunOnce(false);
201
202 // Verify that the correct HTTP error code is sent to the client. Because
203 // curl_perform will fail, this will be reported as an internal server error.
204 const std::string expected_http_reply =
205 "HTTP/1.1 500 Internal Server Error - Origin: local proxy\r\n\r\n";
206 std::vector<char> buf(expected_http_reply.size());
207 ASSERT_TRUE(base::ReadFromFD(client_socket->fd(), buf.data(), buf.size()));
208 buf.push_back('\0');
209 const std::string actual_http_reply(buf.data());
210 EXPECT_EQ(expected_http_reply, actual_http_reply);
211 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
Andreea Costinase45d54b2020-03-10 09:21:14 +0100212}
213
214// Tests the |OnConnectionSetupFinished| callback is handled correctly in case
215// of success or error.
216TEST_F(ServerProxyTest, HandlePendingJobs) {
217 int connection_count = 100;
218 int success_count = 51;
219 int failure_count = 49;
220 // Create |connection_count| connections.
221 for (int i = 0; i < connection_count; ++i) {
222 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900223 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100224 auto mock_connect_job = std::make_unique<MockProxyConnectJob>(
225 std::move(client_socket), "" /* credentials */,
226 base::BindOnce([](const std::string& target_url,
227 OnProxyResolvedCallback callback) {}),
228 base::BindOnce(&ServerProxy::OnConnectionSetupFinished,
229 base::Unretained(server_proxy_.get())));
230 server_proxy_->pending_connect_jobs_[mock_connect_job.get()] =
231 std::move(mock_connect_job);
232 }
233 // Resolve |failure_count| pending connections with error.
234 for (int i = 0; i < failure_count; ++i) {
235 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
236 std::move(job_iter->second->setup_finished_callback_)
237 .Run(nullptr, job_iter->first);
238 }
239 // Expect failed requests have been cleared from the pending list and no
240 // forwarder.
241 EXPECT_EQ(success_count, server_proxy_->pending_connect_jobs_.size());
242 EXPECT_EQ(0, server_proxy_->forwarders_.size());
243
244 // Resolve |success_count| successful connections.
245 for (int i = 0; i < success_count; ++i) {
Garrick Evans3388a032020-03-24 11:25:55 +0900246 auto fwd = std::make_unique<patchpanel::SocketForwarder>(
Andreea Costinase45d54b2020-03-10 09:21:14 +0100247 "" /* thread name */,
Garrick Evans3388a032020-03-24 11:25:55 +0900248 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM),
249 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM));
Andreea Costinase45d54b2020-03-10 09:21:14 +0100250 fwd->Start();
251 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
252 std::move(job_iter->second->setup_finished_callback_)
253 .Run(std::move(fwd), job_iter->first);
254 }
255
256 // Expect the successful requests to have been cleared and |success_count|
257 // active forwarders.
258 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
259 EXPECT_EQ(success_count, server_proxy_->forwarders_.size());
260}
261
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200262// Test to ensure proxy resolution requests are correctly handled if the
263// associated job is canceled before resolution.
264TEST_F(ServerProxyTest, HandleCanceledJobWhilePendingProxyResolution) {
265 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
266 server_proxy_->listening_port_ = 3129;
267 // Redirect the worker stdin and stdout pipes.
268 RedirectStdPipes();
269 server_proxy_->CreateListeningSocket();
270 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
271 brillo_loop_.RunOnce(false);
272
273 struct sockaddr_in ipv4addr;
274 ipv4addr.sin_family = AF_INET;
275 ipv4addr.sin_port = htons(3129);
276 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
277
278 auto client_socket =
279 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
280 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
281 sizeof(ipv4addr)));
282 brillo_loop_.RunOnce(false);
283
284 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
285 const std::string_view http_req =
286 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
287 client_socket->SendTo(http_req.data(), http_req.size());
288
289 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
290 .WillOnce(Return(stdout_write_fd_.get()));
291 brillo_loop_.RunOnce(false);
292
293 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
294 server_proxy_->pending_connect_jobs_.clear();
295
296 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
297 server_proxy_->OnProxyResolved("https://www.example.server.com:443", {});
298
299 EXPECT_EQ(0, server_proxy_->pending_proxy_resolution_requests_.size());
300}
301
Andreea Costinas44cefa22020-03-09 09:07:39 +0100302} // namespace system_proxy