xref: /haiku/src/tests/kits/net/service/TestServer.cpp (revision adcf5b05a8ca9e17407aa4640675c3873c9f0a6c)
1 /*
2  * Copyright 2020 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *   Kyle Ambroff-Kao, kyle@ambroffkao.com
7  */
8 #include "TestServer.h"
9 
10 #include <netinet/in.h>
11 #include <posix/libgen.h>
12 #include <sstream>
13 #include <string>
14 #include <sys/socket.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17 #include <vector>
18 
19 #include <AutoDeleter.h>
20 #include <TestShell.h>
21 
22 
23 namespace {
24 
25 template <typename T>
26 std::string to_string(T value)
27 {
28 	std::ostringstream s;
29 	s << value;
30 	return s.str();
31 }
32 
33 
34 void exec(const std::vector<std::string>& args)
35 {
36 	const char** argv = new const char*[args.size() + 1];
37 	ArrayDeleter<const char*> _(argv);
38 
39 	for (size_t i = 0; i < args.size(); ++i) {
40 		argv[i] = args[i].c_str();
41 	}
42 	argv[args.size()] = NULL;
43 
44 	execv(args[0].c_str(), const_cast<char* const*>(argv));
45 }
46 
47 
48 // Return the path of a file path relative to this source file.
49 std::string TestFilePath(const std::string& relativePath)
50 {
51 	char *testFileSource = strdup(__FILE__);
52 	MemoryDeleter _(testFileSource);
53 
54 	std::string testSrcDir(::dirname(testFileSource));
55 
56 	return testSrcDir + "/" + relativePath;
57 }
58 
59 }
60 
61 
62 RandomTCPServerPort::RandomTCPServerPort()
63 	:
64 	fInitStatus(B_NOT_INITIALIZED),
65 	fSocketFd(-1),
66 	fServerPort(0)
67 {
68 	// Create socket with port 0 to get an unused one selected by the
69 	// kernel.
70 	int socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
71 	if (socket_fd == -1) {
72 		fprintf(
73 			stderr,
74 			"ERROR: Unable to create socket: %s\n",
75 			strerror(errno));
76 		fInitStatus = B_ERROR;
77 		return;
78 	}
79 
80 	fSocketFd = socket_fd;
81 
82 	// We may quickly reclaim the same socket between test runs, so allow
83 	// for reuse.
84 	{
85 		int reuse = 1;
86 		int result = ::setsockopt(
87 			socket_fd,
88 			SOL_SOCKET,
89 			SO_REUSEPORT,
90 			&reuse,
91 			sizeof(reuse));
92 		if (result == -1) {
93 			fInitStatus = errno;
94 			fprintf(
95 				stderr,
96 				"ERROR: Unable to set socket options on fd %d: %s\n",
97 				socket_fd,
98 				strerror(fInitStatus));
99 			return;
100 		}
101 	}
102 
103 	// Bind to loopback 127.0.0.1
104 	struct sockaddr_in server_address;
105 	server_address.sin_family = AF_INET;
106 	server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
107 	int bind_result = ::bind(
108 		socket_fd,
109 		reinterpret_cast<struct sockaddr*>(&server_address),
110 		sizeof(server_address));
111 	if (bind_result == -1) {
112 		fInitStatus = errno;
113 		fprintf(
114 			stderr,
115 			"ERROR: Unable to bind to loopback interface: %s\n",
116 			strerror(fInitStatus));
117 		return;
118 	}
119 
120 	// Listen is apparently required before getsockname will work.
121 	if (::listen(socket_fd, 32) == -1) {
122 		fInitStatus = errno;
123 		fprintf(stderr, "ERROR: listen() failed: %s\n", strerror(fInitStatus));
124 
125 		return;
126 	}
127 
128 	// Now get the port from the socket.
129 	socklen_t server_address_length = sizeof(server_address);
130 	::getsockname(
131 		socket_fd,
132 		reinterpret_cast<struct sockaddr*>(&server_address),
133 		&server_address_length);
134 	fServerPort = ntohs(server_address.sin_port);
135 
136 	fInitStatus = B_OK;
137 }
138 
139 
140 RandomTCPServerPort::~RandomTCPServerPort()
141 {
142 	if (fSocketFd != -1) {
143 		::close(fSocketFd);
144 		fSocketFd = -1;
145 		fInitStatus = B_NOT_INITIALIZED;
146 	}
147 }
148 
149 
150 status_t RandomTCPServerPort::InitCheck() const
151 {
152 	return fInitStatus;
153 }
154 
155 
156 int RandomTCPServerPort::FileDescriptor() const
157 {
158 	return fSocketFd;
159 }
160 
161 
162 uint16_t RandomTCPServerPort::Port() const
163 {
164 	return fServerPort;
165 }
166 
167 
168 ChildProcess::ChildProcess()
169 	:
170 	fChildPid(-1)
171 {
172 }
173 
174 
175 ChildProcess::~ChildProcess()
176 {
177 	if (fChildPid != -1) {
178 		::kill(fChildPid, SIGTERM);
179 
180 		pid_t result = -1;
181 		while (result != fChildPid) {
182 			result = ::waitpid(fChildPid, NULL, 0);
183 		}
184 	}
185 }
186 
187 
188 // The job of this method is to spawn a child process that will later be killed
189 // by the destructor.
190 status_t ChildProcess::Start(const std::vector<std::string>& args)
191 {
192 	if (fChildPid != -1) {
193 		return B_ALREADY_RUNNING;
194 	}
195 
196 	pid_t child = ::fork();
197 	if (child < 0)
198 		return B_ERROR;
199 
200 	if (child > 0) {
201 		fChildPid = child;
202 		return B_OK;
203 	}
204 
205 	// This is the child process. We can exec image provided in args.
206 	exec(args);
207 
208 	// If we reach this point we failed to load the Python image.
209 	std::ostringstream ostr;
210 
211 	for (std::vector<std::string>::const_iterator iter = args.cbegin();
212 		 iter != args.end();
213 		 ++iter) {
214 		ostr << " " << *iter;
215 	}
216 
217 	fprintf(
218 		stderr,
219 		"Unable to spawn `%s': %s\n",
220 		ostr.str().c_str(),
221 		strerror(errno));
222 	exit(1);
223 }
224 
225 
226 TestServer::TestServer(TestServerMode mode)
227 	:
228 	fMode(mode)
229 {
230 }
231 
232 
233 // Start a child testserver.py process with the random TCP port chosen by
234 // fPort.
235 status_t TestServer::Start()
236 {
237 	if (fPort.InitCheck() != B_OK) {
238 		return fPort.InitCheck();
239 	}
240 
241 	// This is the child process. We can exec the server process.
242 	std::vector<std::string> child_process_args;
243 	child_process_args.push_back("/bin/python3");
244 	child_process_args.push_back(TestFilePath("testserver.py"));
245 	child_process_args.push_back("--port");
246 	child_process_args.push_back(to_string(fPort.Port()));
247 	child_process_args.push_back("--fd");
248 	child_process_args.push_back(to_string(fPort.FileDescriptor()));
249 
250 	if (fMode == TEST_SERVER_MODE_HTTPS) {
251 		child_process_args.push_back("--use-tls");
252 	}
253 
254 	// After this the child process has started. It may take a short amount of
255 	// time before the child process is ready to call accept(), but that's OK.
256 	//
257 	// Since the socket has already been created above, the tests will not
258 	// get ECONNREFUSED and will block until the child process calls
259 	// accept(). So we don't have to busy loop here waiting for a
260 	// connection to the child.
261 	return fChildProcess.Start(child_process_args);
262 }
263 
264 
265 BUrl TestServer::BaseUrl() const
266 {
267 	std::string scheme;
268 	switch(fMode) {
269 	case TEST_SERVER_MODE_HTTP:
270 		scheme = "http://";
271 		break;
272 
273 	case TEST_SERVER_MODE_HTTPS:
274 		scheme = "https://";
275 		break;
276 	}
277 
278 	std::string port_string = to_string(fPort.Port());
279 
280 	std::string baseUrl = scheme + "127.0.0.1:" + port_string + "/";
281 	return BUrl(baseUrl.c_str());
282 }
283 
284 
285 // Start a child proxy.py process using the random TCP port chosen by fPort.
286 status_t TestProxyServer::Start()
287 {
288 	if (fPort.InitCheck() != B_OK) {
289 		return fPort.InitCheck();
290 	}
291 
292 	std::vector<std::string> child_process_args;
293 	child_process_args.push_back("/bin/python3");
294 	child_process_args.push_back(TestFilePath("proxy.py"));
295 	child_process_args.push_back("--port");
296 	child_process_args.push_back(to_string(fPort.Port()));
297 	child_process_args.push_back("--fd");
298 	child_process_args.push_back(to_string(fPort.FileDescriptor()));
299 
300 	// After this the child process has started. It may take a short amount of
301 	// time before the child process is ready to call accept(), but that's OK.
302 	//
303 	// Since the socket has already been created above, the tests will not
304 	// get ECONNREFUSED and will block until the child process calls
305 	// accept(). So we don't have to busy loop here waiting for a
306 	// connection to the child.
307 	return fChildProcess.Start(child_process_args);
308 }
309 
310 
311 uint16_t TestProxyServer::Port() const
312 {
313 	return fPort.Port();
314 }
315