| /*****************************************************************************\ |
| * x11_forwarding.c - setup x11 port forwarding |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <grp.h> |
| #include <netinet/in.h> |
| #include <poll.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| |
| #include "src/common/eio.h" |
| #include "src/common/half_duplex.h" |
| #include "src/common/macros.h" |
| #include "src/common/net.h" |
| #include "src/common/read_config.h" |
| #include "src/common/uid.h" |
| #include "src/common/x11_util.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #include "src/interfaces/conn.h" |
| |
| #include "src/slurmd/slurmstepd/slurmstepd.h" |
| #include "src/slurmd/slurmstepd/slurmstepd_job.h" |
| |
| static uint32_t job_id = NO_VAL; |
| static uid_t job_uid; |
| |
| static bool local_xauthority = false; |
| static char hostname[HOST_NAME_MAX] = {0}; |
| |
| static eio_handle_t *eio_handle = NULL; |
| |
| /* Target salloc/srun host/port */ |
| static slurm_addr_t alloc_node; |
| /* X11 display hostname on target, or UNIX socket. */ |
| static char *x11_target = NULL; |
| /* X11 display port on target (if not a UNIX socket). */ |
| static uint16_t x11_target_port = 0; |
| static uint16_t protocol_version = SLURM_PROTOCOL_VERSION; |
| |
| static void *_eio_thread(void *arg) |
| { |
| eio_handle_mainloop(eio_handle); |
| return NULL; |
| } |
| |
| static bool _x11_socket_readable(eio_obj_t *obj) |
| { |
| if (obj->shutdown) { |
| if (obj->fd != -1) |
| close(obj->fd); |
| obj->fd = -1; |
| return false; |
| } |
| return true; |
| } |
| |
| static int _x11_socket_read(eio_obj_t *obj, list_t *objs) |
| { |
| void *tls_conn = NULL; |
| slurm_msg_t req, resp; |
| net_forward_msg_t rpc; |
| slurm_addr_t sin; |
| int *local, *remote; |
| char *srun_tls_cert = obj->arg; |
| int rc; |
| |
| local = xmalloc(sizeof(*local)); |
| remote = xmalloc(sizeof(*remote)); |
| |
| if ((*local = slurm_accept_conn(obj->fd, &sin)) == -1) { |
| error("accept call failure, shutting down"); |
| goto shutdown; |
| } |
| |
| if (!(tls_conn = slurm_open_msg_conn(&alloc_node, srun_tls_cert))) { |
| error("%s: slurm_open_msg_conn(%pA): %m", |
| __func__, &alloc_node); |
| goto shutdown; |
| } |
| |
| *remote = conn_g_get_fd(tls_conn); |
| |
| rpc.job_id = job_id; |
| rpc.flags = 0; |
| rpc.port = x11_target_port; |
| rpc.target = x11_target; |
| |
| slurm_msg_t_init(&req); |
| slurm_msg_t_init(&resp); |
| |
| req.msg_type = SRUN_NET_FORWARD; |
| req.protocol_version = protocol_version; |
| slurm_msg_set_r_uid(&req, job_uid); |
| req.data = &rpc; |
| |
| slurm_send_recv_msg(tls_conn, &req, &resp, 0); |
| |
| if (resp.msg_type != RESPONSE_SLURM_RC) { |
| error("Unexpected response on setup, forwarding failed."); |
| slurm_free_msg_members(&resp); |
| goto shutdown; |
| } |
| |
| if ((rc = slurm_get_return_code(resp.msg_type, resp.data))) { |
| error("Error setting up X11 forwarding from remote: %s", |
| slurm_strerror(rc)); |
| slurm_free_msg_members(&resp); |
| goto shutdown; |
| } |
| |
| slurm_free_msg_members(&resp); |
| |
| net_set_nodelay(*local, true, NULL); |
| net_set_nodelay(*remote, true, NULL); |
| |
| if (half_duplex_add_objs_to_handle(eio_handle, local, remote, |
| tls_conn)) { |
| goto shutdown; |
| } |
| |
| debug("%s: X11 forwarding setup successful", __func__); |
| |
| return SLURM_SUCCESS; |
| |
| shutdown: |
| debug2("%s: error, shutting down", __func__); |
| if (*local != -1) |
| close(*local); |
| xfree(local); |
| xfree(remote); |
| |
| return SLURM_ERROR; |
| } |
| |
| extern int shutdown_x11_forward(stepd_step_rec_t *step) |
| { |
| int rc = SLURM_SUCCESS; |
| |
| debug("x11 forwarding shutdown in progress"); |
| if (eio_handle) |
| eio_signal_shutdown(eio_handle); |
| |
| if (step->x11_xauthority) { |
| if (local_xauthority) { |
| if (unlink(step->x11_xauthority)) { |
| error("%s: problem unlinking xauthority file %s: %m", |
| __func__, step->x11_xauthority); |
| rc = SLURM_ERROR; |
| } |
| } else |
| rc = x11_delete_xauth(step->x11_xauthority, hostname, |
| step->x11_display); |
| } |
| |
| info("x11 forwarding shutdown complete"); |
| return rc; |
| } |
| |
| /* |
| * Bind to a local port for X11 connections. Each connection will setup a |
| * separate tunnel through the remote salloc/srun process. |
| * |
| * IN: job |
| * OUT: SLURM_SUCCESS or SLURM_ERROR |
| */ |
| extern int setup_x11_forward(stepd_step_rec_t *step) |
| { |
| int listen_socket = -1; |
| uint16_t port; |
| /* |
| * Range of ports we'll accept locally. This corresponds to X11 |
| * displays of 20 through 99. Intentionally skipping [10 - 19] |
| * as 'ssh -X' will start at 10 and work up from there. |
| */ |
| uint16_t ports[2] = {6020, 6099}; |
| |
| /* |
| * EIO handles both the local listening socket, as well as the individual |
| * forwarded connections. |
| */ |
| eio_obj_t *obj; |
| static struct io_operations x11_socket_ops = { |
| .readable = _x11_socket_readable, |
| .handle_read = _x11_socket_read, |
| }; |
| srun_info_t *srun = list_peek(step->sruns); |
| /* This should always be set to something else we have a bug. */ |
| xassert(srun && srun->protocol_version); |
| protocol_version = srun->protocol_version; |
| |
| job_id = step->step_id.job_id; |
| job_uid = step->uid; |
| x11_target = xstrdup(step->x11_target); |
| x11_target_port = step->x11_target_port; |
| |
| slurm_set_addr(&alloc_node, step->x11_alloc_port, step->x11_alloc_host); |
| |
| debug("X11Parameters: %s", slurm_conf.x11_params); |
| |
| if (xstrcasestr(slurm_conf.x11_params, "home_xauthority")) { |
| char *home = xstrdup(step->pw_dir); |
| if (!home && !(home = uid_to_dir(step->uid))) { |
| error("Could not look up user home directory"); |
| goto shutdown; |
| } |
| step->x11_xauthority = xstrdup_printf("%s/.Xauthority", home); |
| xfree(home); |
| } else { |
| /* use a node-local XAUTHORITY file instead of ~/.Xauthority */ |
| local_xauthority = true; |
| step->x11_xauthority = slurm_get_tmp_fs(conf->node_name); |
| xstrcat(step->x11_xauthority, "/.Xauthority-XXXXXX"); |
| } |
| |
| /* |
| * Slurm uses the shortened hostname by default (and discards any |
| * domain component), which can cause problems for some sites. |
| * So retrieve the raw value from gethostname() again. |
| */ |
| if (gethostname(hostname, sizeof(hostname))) |
| fatal("%s: gethostname failed: %m", __func__); |
| |
| if (net_stream_listen_ports(&listen_socket, &port, ports, true) == -1) { |
| error("failed to open local socket"); |
| goto shutdown; |
| } |
| |
| step->x11_display = port - X11_TCP_PORT_OFFSET; |
| |
| info("X11 forwarding established on DISPLAY=%s:%d.0", |
| hostname, step->x11_display); |
| |
| eio_handle = eio_handle_create(0); |
| obj = eio_obj_create(listen_socket, &x11_socket_ops, NULL); |
| obj->arg = xstrdup(srun->tls_cert); |
| |
| eio_new_initial_obj(eio_handle, obj); |
| slurm_thread_create_detached(_eio_thread, NULL); |
| |
| return SLURM_SUCCESS; |
| |
| shutdown: |
| xfree(x11_target); |
| step->x11_display = 0; |
| xfree(step->x11_xauthority); |
| if (listen_socket != -1) |
| close(listen_socket); |
| |
| return SLURM_ERROR; |
| } |