blob: 4c67f72e025ccf7c7424877e2153c7b5a9d98559 [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"
27
28namespace {
29constexpr char kProxyServerUrl[] = "172.0.0.1:8888";
30} // namespace
31
32namespace system_proxy {
33
34using ::testing::_;
35using ::testing::Return;
36
37class ProxyConnectJobTest : public ::testing::Test {
38 public:
39 ProxyConnectJobTest() = default;
40 ProxyConnectJobTest(const ProxyConnectJobTest&) = delete;
41 ProxyConnectJobTest& operator=(const ProxyConnectJobTest&) = delete;
42 ~ProxyConnectJobTest() = default;
43
44 void SetUp() override {
45 int fds[2];
46 ASSERT_NE(-1,
47 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
48 0 /* protocol */, fds));
49 cros_client_socket_ =
Garrick Evans3388a032020-03-24 11:25:55 +090050 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
Andreea Costinase45d54b2020-03-10 09:21:14 +010051
52 connect_job_ = std::make_unique<ProxyConnectJob>(
Garrick Evans3388a032020-03-24 11:25:55 +090053 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinase45d54b2020-03-10 09:21:14 +010054 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
55 base::Unretained(this)),
56 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
57 base::Unretained(this)));
Andreea Costinase45d54b2020-03-10 09:21:14 +010058 }
59
60 protected:
61 void ResolveProxy(
62 const std::string& target_url,
63 base::OnceCallback<void(const std::list<std::string>&)> callback) {
64 std::move(callback).Run({kProxyServerUrl});
65 }
66
67 void OnConnectionSetupFinished(
Garrick Evans3388a032020-03-24 11:25:55 +090068 std::unique_ptr<patchpanel::SocketForwarder> fwd,
Andreea Costinase45d54b2020-03-10 09:21:14 +010069 ProxyConnectJob* connect_job) {
70 ASSERT_EQ(connect_job, connect_job_.get());
71 }
72
73 std::unique_ptr<ProxyConnectJob> connect_job_;
74 base::MessageLoopForIO loop_;
75 brillo::BaseMessageLoop brillo_loop_{&loop_};
Garrick Evans3388a032020-03-24 11:25:55 +090076 std::unique_ptr<patchpanel::Socket> cros_client_socket_;
Andreea Costinas08a5d182020-04-29 22:12:47 +020077
78 private:
79 FRIEND_TEST(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled);
Andreea Costinase45d54b2020-03-10 09:21:14 +010080};
81
82TEST_F(ProxyConnectJobTest, SuccessfulConnection) {
Andreea Costinas08a5d182020-04-29 22:12:47 +020083 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +010084 char validConnRequest[] =
85 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
86 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
87 brillo_loop_.RunOnce(false);
88
Andreea Costinasa2246592020-04-12 23:24:01 +020089 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
Andreea Costinase45d54b2020-03-10 09:21:14 +010090 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
91 EXPECT_EQ(kProxyServerUrl, connect_job_->proxy_servers_.front());
92}
93
Andreea Costinased3f9782020-05-20 17:09:46 +020094TEST_F(ProxyConnectJobTest, SuccessfulConnectionAltEnding) {
95 connect_job_->Start();
96 char validConnRequest[] =
97 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\n";
98 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
99 brillo_loop_.RunOnce(false);
100
101 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
102 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
103 EXPECT_EQ(kProxyServerUrl, connect_job_->proxy_servers_.front());
104}
105
Andreea Costinase45d54b2020-03-10 09:21:14 +0100106TEST_F(ProxyConnectJobTest, BadHttpRequestWrongMethod) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200107 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100108 char badConnRequest[] = "GET www.example.server.com:443 HTTP/1.1\r\n\r\n";
109 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
110 brillo_loop_.RunOnce(false);
111
112 EXPECT_EQ("", connect_job_->target_url_);
113 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
114 const std::string expected_http_response =
115 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
116 std::vector<char> buf(expected_http_response.size());
117 ASSERT_TRUE(
118 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
119 std::string actual_response(buf.data(), buf.size());
120 EXPECT_EQ(expected_http_response, actual_response);
121}
122
123TEST_F(ProxyConnectJobTest, BadHttpRequestNoEmptyLine) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200124 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100125 // No empty line after http message.
126 char badConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
127 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
128 brillo_loop_.RunOnce(false);
129
130 EXPECT_EQ("", connect_job_->target_url_);
131 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
132 const std::string expected_http_response =
133 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
134 std::vector<char> buf(expected_http_response.size());
135 ASSERT_TRUE(
136 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
137 std::string actual_response(buf.data(), buf.size());
138 EXPECT_EQ(expected_http_response, actual_response);
139}
140
Andreea Costinas08a5d182020-04-29 22:12:47 +0200141TEST_F(ProxyConnectJobTest, WaitClientConnectTimeout) {
142 // Add a TaskRunner where we can control time.
143 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
144 new base::TestMockTimeTaskRunner()};
145 loop_.SetTaskRunner(task_runner);
146 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
147
148 connect_job_->Start();
149
150 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
151 // Move the time ahead so that the client connection timeout callback is
152 // triggered.
153 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
154
155 const std::string expected_http_response =
156 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
157 std::vector<char> buf(expected_http_response.size());
158 ASSERT_TRUE(
159 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
160 std::string actual_response(buf.data(), buf.size());
161
162 EXPECT_EQ(expected_http_response, actual_response);
163}
164
165// Check that the client connect timeout callback is not fired if the owning
166// proxy connect job is destroyed.
167TEST_F(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled) {
168 // Add a TaskRunner where we can control time.
169 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
170 new base::TestMockTimeTaskRunner()};
171 loop_.SetTaskRunner(task_runner);
172 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
173
174 // Create a proxy connect job and start the client connect timeout counter.
175 {
176 int fds[2];
177 ASSERT_NE(-1,
178 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
179 0 /* protocol */, fds));
180 auto client_socket =
181 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
182
183 auto connect_job = std::make_unique<ProxyConnectJob>(
184 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
185 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
186 base::Unretained(this)),
187 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
188 base::Unretained(this)));
189 // Post the timeout task.
190 connect_job->Start();
191 EXPECT_TRUE(task_runner->HasPendingTask());
192 }
193 // Check that the task was canceled.
194 EXPECT_FALSE(task_runner->HasPendingTask());
195}
196
Andreea Costinase45d54b2020-03-10 09:21:14 +0100197} // namespace system_proxy