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