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