blob: a6338bfbfdac45f99a884a900fa0835231f19040 [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
Andreea Costinase45d54b2020-03-10 09:21:14 +010015#include <arc/network/socket.h>
16#include <arc/network/socket_forwarder.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010017#include <base/bind.h>
18#include <base/bind_helpers.h>
19#include <base/callback_helpers.h>
20#include <base/files/file_util.h>
21#include <base/files/scoped_file.h>
22#include <base/message_loop/message_loop.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010023#include <base/strings/string_util.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010024#include <brillo/dbus/async_event_sequencer.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010025#include <brillo/message_loops/base_message_loop.h>
26
27#include "bindings/worker_common.pb.h"
28#include "system-proxy/protobuf_util.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010029#include "system-proxy/proxy_connect_job.h"
Andreea Costinas44cefa22020-03-09 09:07:39 +010030
31namespace system_proxy {
32namespace {
Andreea Costinase45d54b2020-03-10 09:21:14 +010033constexpr char kUsername[] = "proxy:user";
34constexpr char kUsernameEncoded[] = "proxy%3Auser";
35constexpr char kPassword[] = "proxy password";
36constexpr char kPasswordEncoded[] = "proxy%20password";
Andreea Costinas44cefa22020-03-09 09:07:39 +010037constexpr int kTestPort = 3128;
Andreea Costinas5862b102020-03-19 14:45:36 +010038constexpr char kFakeProxyAddress[] = "http://127.0.0.1";
Andreea Costinase45d54b2020-03-10 09:21:14 +010039
Andreea Costinas44cefa22020-03-09 09:07:39 +010040} // namespace
41
Andreea Costinase45d54b2020-03-10 09:21:14 +010042using ::testing::_;
Andreea Costinas44cefa22020-03-09 09:07:39 +010043using ::testing::Return;
44
45class MockServerProxy : public ServerProxy {
46 public:
47 explicit MockServerProxy(base::OnceClosure quit_closure)
48 : ServerProxy(std::move(quit_closure)) {}
49 MockServerProxy(const MockServerProxy&) = delete;
50 MockServerProxy& operator=(const MockServerProxy&) = delete;
51 ~MockServerProxy() override = default;
52
53 MOCK_METHOD(int, GetStdinPipe, (), (override));
Andreea Costinas5862b102020-03-19 14:45:36 +010054 MOCK_METHOD(int, GetStdoutPipe, (), (override));
Andreea Costinas44cefa22020-03-09 09:07:39 +010055};
56
Andreea Costinase45d54b2020-03-10 09:21:14 +010057class MockProxyConnectJob : public ProxyConnectJob {
58 public:
59 MockProxyConnectJob(std::unique_ptr<arc_networkd::Socket> socket,
60 const std::string& credentials,
61 ResolveProxyCallback resolve_proxy_callback,
62 OnConnectionSetupFinishedCallback setup_finished_callback)
63 : ProxyConnectJob(std::move(socket),
64 credentials,
65 std::move(resolve_proxy_callback),
66 std::move(setup_finished_callback)) {}
67 MockProxyConnectJob(const MockProxyConnectJob&) = delete;
68 MockProxyConnectJob& operator=(const MockProxyConnectJob&) = delete;
69 ~MockProxyConnectJob() override = default;
70
71 MOCK_METHOD(bool, Start, (), (override));
72};
73
Andreea Costinas44cefa22020-03-09 09:07:39 +010074class ServerProxyTest : public ::testing::Test {
75 public:
76 ServerProxyTest() {
77 server_proxy_ =
78 std::make_unique<MockServerProxy>(brillo_loop_.QuitClosure());
79 }
80
81 ServerProxyTest(const ServerProxyTest&) = delete;
82 ServerProxyTest& operator=(const ServerProxyTest&) = delete;
83 ~ServerProxyTest() override {}
84
85 protected:
Andreea Costinas5862b102020-03-19 14:45:36 +010086 // Redirects the standard streams of the worker so that the tests can write
87 // data in the worker's stdin input and read data from the worker's stdout
88 // output.
Andreea Costinas44cefa22020-03-09 09:07:39 +010089 void RedirectStdPipes() {
90 int fds[2];
91 CHECK(base::CreateLocalNonBlockingPipe(fds));
Andreea Costinas5862b102020-03-19 14:45:36 +010092 stdin_read_fd_.reset(fds[0]);
93 stdin_write_fd_.reset(fds[1]);
94 CHECK(base::CreateLocalNonBlockingPipe(fds));
95 stdout_read_fd_.reset(fds[0]);
96 stdout_write_fd_.reset(fds[1]);
Andreea Costinas44cefa22020-03-09 09:07:39 +010097
Andreea Costinas5862b102020-03-19 14:45:36 +010098 ON_CALL(*server_proxy_, GetStdinPipe())
99 .WillByDefault(Return(stdin_read_fd_.get()));
100 // Don't redirect all the calls to |stdout_write_fd_| or the test result
101 // will not be printed in the console. Instead, when wanting to read the
102 // standard output, set the expectation to once return |stdout_write_fd_|.
103 ON_CALL(*server_proxy_, GetStdoutPipe())
104 .WillByDefault(Return(STDOUT_FILENO));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100105 server_proxy_->Init();
106 }
107 // SystemProxyAdaptor instance that creates fake worker processes.
108 std::unique_ptr<MockServerProxy> server_proxy_;
109 base::MessageLoopForIO loop_;
110 brillo::BaseMessageLoop brillo_loop_{&loop_};
Andreea Costinas5862b102020-03-19 14:45:36 +0100111 base::ScopedFD stdin_read_fd_, stdin_write_fd_, stdout_read_fd_,
112 stdout_write_fd_;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100113};
114
115TEST_F(ServerProxyTest, FetchCredentials) {
116 Credentials credentials;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100117 credentials.set_username(kUsername);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100118 credentials.set_password(kPassword);
119 WorkerConfigs configs;
120 *configs.mutable_credentials() = credentials;
121 RedirectStdPipes();
122
Andreea Costinas5862b102020-03-19 14:45:36 +0100123 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100124
125 brillo_loop_.RunOnce(false);
126
Andreea Costinase45d54b2020-03-10 09:21:14 +0100127 std::string expected_credentials =
128 base::JoinString({kUsernameEncoded, kPasswordEncoded}, ":");
129 EXPECT_EQ(server_proxy_->credentials_, expected_credentials);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100130}
131
132TEST_F(ServerProxyTest, FetchListeningAddress) {
133 SocketAddress address;
134 address.set_addr(INADDR_ANY);
135 address.set_port(kTestPort);
136 WorkerConfigs configs;
137 *configs.mutable_listening_address() = address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100138 // Redirect the worker stdin and stdout pipes.
Andreea Costinas5862b102020-03-19 14:45:36 +0100139 RedirectStdPipes();
140 // Send the config to the worker's stdin input.
141 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100142 brillo_loop_.RunOnce(false);
143
144 EXPECT_EQ(server_proxy_->listening_addr_, INADDR_ANY);
145 EXPECT_EQ(server_proxy_->listening_port_, kTestPort);
146}
147
Andreea Costinas5862b102020-03-19 14:45:36 +0100148// Tests that ServerProxy handles the basic flow of a connect request:
149// - server accepts a connection a creates a job for it until the connection is
150// finished;
151// - the connect request from the client socket is read and parsed;
152// - proxy resolution request is correctly handled by the job and ServerProxy;
153// - client is sent an HTTP error code in case of failure;
154// - the failed connection job is removed from the queue.
Andreea Costinase45d54b2020-03-10 09:21:14 +0100155TEST_F(ServerProxyTest, HandleConnectRequest) {
156 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
157 server_proxy_->listening_port_ = kTestPort;
158 // Redirect the worker stdin and stdout pipes.
159 RedirectStdPipes();
160 server_proxy_->CreateListeningSocket();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100161 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
162 brillo_loop_.RunOnce(false);
163
164 struct sockaddr_in ipv4addr;
165 ipv4addr.sin_family = AF_INET;
166 ipv4addr.sin_port = htons(kTestPort);
167 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
168
169 auto client_socket =
170 std::make_unique<arc_networkd::Socket>(AF_INET, SOCK_STREAM);
171 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
172 sizeof(ipv4addr)));
173 brillo_loop_.RunOnce(false);
174
175 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
Andreea Costinas5862b102020-03-19 14:45:36 +0100176 const std::string_view http_req =
177 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
178 client_socket->SendTo(http_req.data(), http_req.size());
179
180 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
181 .WillOnce(Return(stdout_write_fd_.get()));
182 brillo_loop_.RunOnce(false);
183 WorkerRequest request;
184 // Read the request from the worker's stdout output.
185 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
186 ASSERT_TRUE(request.has_proxy_resolution_request());
187
188 EXPECT_EQ("http://www.example.server.com:443",
189 request.proxy_resolution_request().target_url());
190
191 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
192
193 // Write reply with a fake proxy to the worker's standard input.
194 ProxyResolutionReply reply;
195 reply.set_target_url(request.proxy_resolution_request().target_url());
196 reply.add_proxy_servers(kFakeProxyAddress);
197 WorkerConfigs configs;
198 *configs.mutable_proxy_resolution_reply() = reply;
199
200 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
201 brillo_loop_.RunOnce(false);
202
203 // Verify that the correct HTTP error code is sent to the client. Because
204 // curl_perform will fail, this will be reported as an internal server error.
205 const std::string expected_http_reply =
206 "HTTP/1.1 500 Internal Server Error - Origin: local proxy\r\n\r\n";
207 std::vector<char> buf(expected_http_reply.size());
208 ASSERT_TRUE(base::ReadFromFD(client_socket->fd(), buf.data(), buf.size()));
209 buf.push_back('\0');
210 const std::string actual_http_reply(buf.data());
211 EXPECT_EQ(expected_http_reply, actual_http_reply);
212 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
Andreea Costinase45d54b2020-03-10 09:21:14 +0100213}
214
215// Tests the |OnConnectionSetupFinished| callback is handled correctly in case
216// of success or error.
217TEST_F(ServerProxyTest, HandlePendingJobs) {
218 int connection_count = 100;
219 int success_count = 51;
220 int failure_count = 49;
221 // Create |connection_count| connections.
222 for (int i = 0; i < connection_count; ++i) {
223 auto client_socket =
224 std::make_unique<arc_networkd::Socket>(AF_INET, SOCK_STREAM);
225 auto mock_connect_job = std::make_unique<MockProxyConnectJob>(
226 std::move(client_socket), "" /* credentials */,
227 base::BindOnce([](const std::string& target_url,
228 OnProxyResolvedCallback callback) {}),
229 base::BindOnce(&ServerProxy::OnConnectionSetupFinished,
230 base::Unretained(server_proxy_.get())));
231 server_proxy_->pending_connect_jobs_[mock_connect_job.get()] =
232 std::move(mock_connect_job);
233 }
234 // Resolve |failure_count| pending connections with error.
235 for (int i = 0; i < failure_count; ++i) {
236 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
237 std::move(job_iter->second->setup_finished_callback_)
238 .Run(nullptr, job_iter->first);
239 }
240 // Expect failed requests have been cleared from the pending list and no
241 // forwarder.
242 EXPECT_EQ(success_count, server_proxy_->pending_connect_jobs_.size());
243 EXPECT_EQ(0, server_proxy_->forwarders_.size());
244
245 // Resolve |success_count| successful connections.
246 for (int i = 0; i < success_count; ++i) {
247 auto fwd = std::make_unique<arc_networkd::SocketForwarder>(
248 "" /* thread name */,
249 std::make_unique<arc_networkd::Socket>(AF_INET, SOCK_STREAM),
250 std::make_unique<arc_networkd::Socket>(AF_INET, SOCK_STREAM));
251 fwd->Start();
252 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
253 std::move(job_iter->second->setup_finished_callback_)
254 .Run(std::move(fwd), job_iter->first);
255 }
256
257 // Expect the successful requests to have been cleared and |success_count|
258 // active forwarders.
259 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
260 EXPECT_EQ(success_count, server_proxy_->forwarders_.size());
261}
262
Andreea Costinas44cefa22020-03-09 09:07:39 +0100263} // namespace system_proxy