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