openssh_fixture.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /* Copyright (C) 2016 Alexander Lamaison
  2. * All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms,
  5. * with or without modification, are permitted provided
  6. * that the following conditions are met:
  7. *
  8. * Redistributions of source code must retain the above
  9. * copyright notice, this list of conditions and the
  10. * following disclaimer.
  11. *
  12. * Redistributions in binary form must reproduce the above
  13. * copyright notice, this list of conditions and the following
  14. * disclaimer in the documentation and/or other materials
  15. * provided with the distribution.
  16. *
  17. * Neither the name of the copyright holder nor the names
  18. * of any other contributors may be used to endorse or
  19. * promote products derived from this software without
  20. * specific prior written permission.
  21. *
  22. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  23. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  24. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  25. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  27. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  28. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  29. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  30. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  31. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  32. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  33. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  34. * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
  35. * OF SUCH DAMAGE.
  36. */
  37. #include "openssh_fixture.h"
  38. #include "libssh2_config.h"
  39. #ifdef HAVE_WINSOCK2_H
  40. #include <winsock2.h>
  41. #endif
  42. #ifdef HAVE_SYS_SOCKET_H
  43. #include <sys/socket.h>
  44. #endif
  45. #ifdef HAVE_ARPA_INET_H
  46. #include <arpa/inet.h>
  47. #endif
  48. #ifdef HAVE_NETINET_IN_H
  49. #include <netinet/in.h>
  50. #endif
  51. #ifdef HAVE_UNISTD_H
  52. #include <unistd.h>
  53. #endif
  54. #include <ctype.h>
  55. #include <stdio.h>
  56. #include <stdlib.h>
  57. #include <string.h>
  58. #include <stdarg.h>
  59. static int run_command_varg(char **output, const char *command, va_list args)
  60. {
  61. FILE *pipe;
  62. char redirect_stderr[] = "%s 2>&1";
  63. char command_buf[BUFSIZ];
  64. char buf[BUFSIZ];
  65. int ret;
  66. size_t buf_len;
  67. if(output) {
  68. *output = NULL;
  69. }
  70. /* Format the command string */
  71. ret = vsnprintf(command_buf, sizeof(command_buf), command, args);
  72. if(ret < 0 || ret >= BUFSIZ) {
  73. fprintf(stderr, "Unable to format command (%s)\n", command);
  74. return -1;
  75. }
  76. /* Rewrite the command to redirect stderr to stdout to we can output it */
  77. if(strlen(command_buf) + strlen(redirect_stderr) >= sizeof(buf)) {
  78. fprintf(stderr, "Unable to rewrite command (%s)\n", command);
  79. return -1;
  80. }
  81. ret = snprintf(buf, sizeof(buf), redirect_stderr, command_buf);
  82. if(ret < 0 || ret >= BUFSIZ) {
  83. fprintf(stderr, "Unable to rewrite command (%s)\n", command);
  84. return -1;
  85. }
  86. fprintf(stdout, "Command: %s\n", command);
  87. #ifdef WIN32
  88. pipe = _popen(buf, "r");
  89. #else
  90. pipe = popen(buf, "r");
  91. #endif
  92. if(!pipe) {
  93. fprintf(stderr, "Unable to execute command '%s'\n", command);
  94. return -1;
  95. }
  96. buf[0] = 0;
  97. buf_len = 0;
  98. while(buf_len < (sizeof(buf) - 1) &&
  99. fgets(&buf[buf_len], sizeof(buf) - buf_len, pipe) != NULL) {
  100. buf_len = strlen(buf);
  101. }
  102. #ifdef WIN32
  103. ret = _pclose(pipe);
  104. #else
  105. ret = pclose(pipe);
  106. #endif
  107. if(ret != 0) {
  108. fprintf(stderr, "Error running command '%s' (exit %d): %s\n",
  109. command, ret, buf);
  110. }
  111. if(output) {
  112. /* command output may contain a trailing newline, so we trim
  113. * whitespace here */
  114. size_t end = strlen(buf);
  115. while(end > 0 && isspace(buf[end - 1])) {
  116. buf[end - 1] = '\0';
  117. }
  118. *output = strdup(buf);
  119. }
  120. return ret;
  121. }
  122. static int run_command(char **output, const char *command, ...)
  123. {
  124. va_list args;
  125. int ret;
  126. va_start(args, command);
  127. ret = run_command_varg(output, command, args);
  128. va_end(args);
  129. return ret;
  130. }
  131. static int build_openssh_server_docker_image(void)
  132. {
  133. return run_command(NULL, "docker build -t libssh2/openssh_server "
  134. "openssh_server");
  135. }
  136. static const char *openssh_server_port(void)
  137. {
  138. return getenv("OPENSSH_SERVER_PORT");
  139. }
  140. static int start_openssh_server(char **container_id_out)
  141. {
  142. const char *container_host_port = openssh_server_port();
  143. if(container_host_port != NULL) {
  144. return run_command(container_id_out,
  145. "docker run --rm -d -p %s:22 "
  146. "libssh2/openssh_server",
  147. container_host_port);
  148. }
  149. else {
  150. return run_command(container_id_out,
  151. "docker run --rm -d -p 22 "
  152. "libssh2/openssh_server");
  153. }
  154. }
  155. static int stop_openssh_server(char *container_id)
  156. {
  157. return run_command(NULL, "docker stop %s", container_id);
  158. }
  159. static const char *docker_machine_name(void)
  160. {
  161. return getenv("DOCKER_MACHINE_NAME");
  162. }
  163. static int is_running_inside_a_container()
  164. {
  165. #ifdef WIN32
  166. return 0;
  167. #else
  168. const char *cgroup_filename = "/proc/self/cgroup";
  169. FILE *f = NULL;
  170. char *line = NULL;
  171. size_t len = 0;
  172. ssize_t read = 0;
  173. int found = 0;
  174. f = fopen(cgroup_filename, "r");
  175. if(f == NULL) {
  176. /* Don't go further, we are not in a container */
  177. return 0;
  178. }
  179. while((read = getline(&line, &len, f)) != -1) {
  180. if(strstr(line, "docker") != NULL) {
  181. found = 1;
  182. break;
  183. }
  184. }
  185. fclose(f);
  186. free(line);
  187. return found;
  188. #endif
  189. }
  190. static unsigned int portable_sleep(unsigned int seconds)
  191. {
  192. #ifdef WIN32
  193. Sleep(seconds);
  194. #else
  195. sleep(seconds);
  196. #endif
  197. }
  198. static int ip_address_from_container(char *container_id, char **ip_address_out)
  199. {
  200. const char *active_docker_machine = docker_machine_name();
  201. if(active_docker_machine != NULL) {
  202. /* This can be flaky when tests run in parallel (see
  203. https://github.com/docker/machine/issues/2612), so we retry a few
  204. times with exponential backoff if it fails */
  205. int attempt_no = 0;
  206. int wait_time = 500;
  207. for(;;) {
  208. int ret = run_command(ip_address_out, "docker-machine ip %s",
  209. active_docker_machine);
  210. if(ret == 0) {
  211. return 0;
  212. }
  213. else if(attempt_no > 5) {
  214. fprintf(
  215. stderr,
  216. "Unable to get IP from docker-machine after %d attempts\n",
  217. attempt_no);
  218. return -1;
  219. }
  220. else {
  221. portable_sleep(wait_time);
  222. ++attempt_no;
  223. wait_time *= 2;
  224. }
  225. }
  226. }
  227. else {
  228. if(is_running_inside_a_container()) {
  229. return run_command(ip_address_out,
  230. "docker inspect --format "
  231. "\"{{ .NetworkSettings.IPAddress }}\""
  232. " %s",
  233. container_id);
  234. }
  235. else {
  236. return run_command(ip_address_out,
  237. "docker inspect --format "
  238. "\"{{ index (index (index "
  239. ".NetworkSettings.Ports "
  240. "\\\"22/tcp\\\") 0) \\\"HostIp\\\" }}\" %s",
  241. container_id);
  242. }
  243. }
  244. }
  245. static int port_from_container(char *container_id, char **port_out)
  246. {
  247. if(is_running_inside_a_container()) {
  248. *port_out = strdup("22");
  249. return 0;
  250. }
  251. else {
  252. return run_command(port_out,
  253. "docker inspect --format "
  254. "\"{{ index (index (index .NetworkSettings.Ports "
  255. "\\\"22/tcp\\\") 0) \\\"HostPort\\\" }}\" %s",
  256. container_id);
  257. }
  258. }
  259. static int open_socket_to_container(char *container_id)
  260. {
  261. char *ip_address = NULL;
  262. char *port_string = NULL;
  263. unsigned long hostaddr;
  264. int sock;
  265. struct sockaddr_in sin;
  266. int counter = 0;
  267. int ret = ip_address_from_container(container_id, &ip_address);
  268. if(ret != 0) {
  269. fprintf(stderr, "Failed to get IP address for container %s\n",
  270. container_id);
  271. ret = -1;
  272. goto cleanup;
  273. }
  274. ret = port_from_container(container_id, &port_string);
  275. if(ret != 0) {
  276. fprintf(stderr, "Failed to get port for container %s\n",
  277. container_id);
  278. ret = -1;
  279. }
  280. /* 0.0.0.0 is returned by Docker for Windows, because the container
  281. is reachable from anywhere. But we cannot connect to 0.0.0.0,
  282. instead we assume localhost and try to connect to 127.0.0.1. */
  283. if(ip_address && strcmp(ip_address, "0.0.0.0") == 0) {
  284. free(ip_address);
  285. ip_address = strdup("127.0.0.1");
  286. }
  287. hostaddr = inet_addr(ip_address);
  288. if(hostaddr == (unsigned long)(-1)) {
  289. fprintf(stderr, "Failed to convert %s host address\n", ip_address);
  290. ret = -1;
  291. goto cleanup;
  292. }
  293. sock = socket(AF_INET, SOCK_STREAM, 0);
  294. if(sock <= 0) {
  295. fprintf(stderr, "Failed to open socket (%d)\n", sock);
  296. ret = -1;
  297. goto cleanup;
  298. }
  299. sin.sin_family = AF_INET;
  300. sin.sin_port = htons((short)strtol(port_string, NULL, 0));
  301. sin.sin_addr.s_addr = hostaddr;
  302. for(counter = 0; counter < 3; ++counter) {
  303. if(connect(sock, (struct sockaddr *)(&sin),
  304. sizeof(struct sockaddr_in)) != 0) {
  305. ret = -1;
  306. fprintf(stderr,
  307. "Connection to %s:%s attempt #%d failed: retrying...\n",
  308. ip_address, port_string, counter);
  309. portable_sleep(1 + 2*counter);
  310. }
  311. else {
  312. ret = sock;
  313. break;
  314. }
  315. }
  316. if(ret == -1) {
  317. fprintf(stderr, "Failed to connect to %s:%s\n",
  318. ip_address, port_string);
  319. goto cleanup;
  320. }
  321. cleanup:
  322. free(ip_address);
  323. free(port_string);
  324. return ret;
  325. }
  326. static char *running_container_id = NULL;
  327. int start_openssh_fixture()
  328. {
  329. int ret;
  330. #ifdef HAVE_WINSOCK2_H
  331. WSADATA wsadata;
  332. ret = WSAStartup(MAKEWORD(2, 0), &wsadata);
  333. if(ret != 0) {
  334. fprintf(stderr, "WSAStartup failed with error: %d\n", ret);
  335. return 1;
  336. }
  337. #endif
  338. ret = build_openssh_server_docker_image();
  339. if(ret == 0) {
  340. return start_openssh_server(&running_container_id);
  341. }
  342. else {
  343. fprintf(stderr, "Failed to build docker image\n");
  344. return ret;
  345. }
  346. }
  347. void stop_openssh_fixture()
  348. {
  349. if(running_container_id) {
  350. stop_openssh_server(running_container_id);
  351. free(running_container_id);
  352. running_container_id = NULL;
  353. }
  354. else {
  355. fprintf(stderr, "Cannot stop container - none started");
  356. }
  357. }
  358. int open_socket_to_openssh_server()
  359. {
  360. return open_socket_to_container(running_container_id);
  361. }