1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 David Strauss
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <arpa/inet.h>
29 #include <sys/fcntl.h>
30 #include <sys/socket.h>
34 #include "sd-daemon.h"
37 #include "socket-util.h"
39 #include "event-util.h"
42 #include "path-util.h"
44 #define BUFFER_SIZE (256 * 1024)
45 #define CONNECTIONS_MAX 256
47 #define _cleanup_freeaddrinfo_ _cleanup_(freeaddrinfop)
48 DEFINE_TRIVIAL_CLEANUP_FUNC(struct addrinfo *, freeaddrinfo);
50 typedef struct Context {
55 typedef struct Connection {
58 int server_fd, client_fd;
59 int server_to_client_buffer[2]; /* a pipe */
60 int client_to_server_buffer[2]; /* a pipe */
62 size_t server_to_client_buffer_full, client_to_server_buffer_full;
63 size_t server_to_client_buffer_size, client_to_server_buffer_size;
65 sd_event_source *server_event_source, *client_event_source;
68 static const char *arg_remote_host = NULL;
70 static void connection_free(Connection *c) {
74 set_remove(c->context->connections, c);
76 sd_event_source_unref(c->server_event_source);
77 sd_event_source_unref(c->client_event_source);
79 safe_close(c->server_fd);
80 safe_close(c->client_fd);
82 safe_close_pair(c->server_to_client_buffer);
83 safe_close_pair(c->client_to_server_buffer);
88 static void context_free(Context *context) {
94 while ((es = set_steal_first(context->listen)))
95 sd_event_source_unref(es);
97 while ((c = set_first(context->connections)))
100 set_free(context->listen);
101 set_free(context->connections);
104 static int get_remote_sockaddr(union sockaddr_union *sa, socklen_t *salen) {
110 if (path_is_absolute(arg_remote_host)) {
111 sa->un.sun_family = AF_UNIX;
112 strncpy(sa->un.sun_path, arg_remote_host, sizeof(sa->un.sun_path)-1);
113 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
115 *salen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa->un.sun_path);
117 } else if (arg_remote_host[0] == '@') {
118 sa->un.sun_family = AF_UNIX;
119 sa->un.sun_path[0] = 0;
120 strncpy(sa->un.sun_path+1, arg_remote_host+1, sizeof(sa->un.sun_path)-2);
121 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
123 *salen = offsetof(union sockaddr_union, un.sun_path) + 1 + strlen(sa->un.sun_path + 1);
126 _cleanup_freeaddrinfo_ struct addrinfo *result = NULL;
127 const char *node, *service;
129 struct addrinfo hints = {
130 .ai_family = AF_UNSPEC,
131 .ai_socktype = SOCK_STREAM,
132 .ai_flags = AI_ADDRCONFIG
135 service = strrchr(arg_remote_host, ':');
137 node = strndupa(arg_remote_host, service - arg_remote_host);
140 node = arg_remote_host;
144 log_debug("Looking up address info for %s:%s", node, service);
145 r = getaddrinfo(node, service, &hints, &result);
147 log_error("Failed to resolve host %s:%s: %s", node, service, gai_strerror(r));
148 return -EHOSTUNREACH;
152 if (result->ai_addrlen > sizeof(union sockaddr_union)) {
153 log_error("Address too long.");
157 memcpy(sa, result->ai_addr, result->ai_addrlen);
158 *salen = result->ai_addrlen;
164 static int connection_create_pipes(Connection *c, int buffer[2], size_t *sz) {
174 r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK);
176 log_error("Failed to allocate pipe buffer: %m");
180 fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE);
182 r = fcntl(buffer[0], F_GETPIPE_SZ);
184 log_error("Failed to get pipe buffer size: %m");
194 static int connection_shovel(
196 int *from, int buffer[2], int *to,
197 size_t *full, size_t *sz,
198 sd_event_source **from_source, sd_event_source **to_source) {
205 assert(buffer[0] >= 0);
206 assert(buffer[1] >= 0);
218 if (*full < *sz && *from >= 0 && *to >= 0) {
219 z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
223 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
224 *from_source = sd_event_source_unref(*from_source);
225 *from = safe_close(*from);
226 } else if (errno != EAGAIN && errno != EINTR) {
227 log_error("Failed to splice: %m");
232 if (*full > 0 && *to >= 0) {
233 z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
237 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
238 *to_source = sd_event_source_unref(*to_source);
239 *to = safe_close(*to);
240 } else if (errno != EAGAIN && errno != EINTR) {
241 log_error("Failed to splice: %m");
250 static int connection_enable_event_sources(Connection *c, sd_event *event);
252 static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
253 Connection *c = userdata;
260 r = connection_shovel(c,
261 &c->server_fd, c->server_to_client_buffer, &c->client_fd,
262 &c->server_to_client_buffer_full, &c->server_to_client_buffer_size,
263 &c->server_event_source, &c->client_event_source);
267 r = connection_shovel(c,
268 &c->client_fd, c->client_to_server_buffer, &c->server_fd,
269 &c->client_to_server_buffer_full, &c->client_to_server_buffer_size,
270 &c->client_event_source, &c->server_event_source);
274 /* EOF on both sides? */
275 if (c->server_fd == -1 && c->client_fd == -1)
278 /* Server closed, and all data written to client? */
279 if (c->server_fd == -1 && c->server_to_client_buffer_full <= 0)
282 /* Client closed, and all data written to server? */
283 if (c->client_fd == -1 && c->client_to_server_buffer_full <= 0)
286 r = connection_enable_event_sources(c, sd_event_source_get_event(s));
294 return 0; /* ignore errors, continue serving */
297 static int connection_enable_event_sources(Connection *c, sd_event *event) {
298 uint32_t a = 0, b = 0;
304 if (c->server_to_client_buffer_full > 0)
306 if (c->server_to_client_buffer_full < c->server_to_client_buffer_size)
309 if (c->client_to_server_buffer_full > 0)
311 if (c->client_to_server_buffer_full < c->client_to_server_buffer_size)
314 if (c->server_event_source)
315 r = sd_event_source_set_io_events(c->server_event_source, a);
316 else if (c->server_fd >= 0)
317 r = sd_event_add_io(event, &c->server_event_source, c->server_fd, a, traffic_cb, c);
322 log_error("Failed to set up server event source: %s", strerror(-r));
326 if (c->client_event_source)
327 r = sd_event_source_set_io_events(c->client_event_source, b);
328 else if (c->client_fd >= 0)
329 r = sd_event_add_io(event, &c->client_event_source, c->client_fd, b, traffic_cb, c);
334 log_error("Failed to set up client event source: %s", strerror(-r));
341 static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
342 Connection *c = userdata;
350 solen = sizeof(error);
351 r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &solen);
353 log_error("Failed to issue SO_ERROR: %m");
358 log_error("Failed to connect to remote host: %s", strerror(error));
362 c->client_event_source = sd_event_source_unref(c->client_event_source);
364 r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size);
368 r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size);
372 r = connection_enable_event_sources(c, sd_event_source_get_event(s));
380 return 0; /* ignore errors, continue serving */
383 static int add_connection_socket(Context *context, sd_event *event, int fd) {
384 union sockaddr_union sa = {};
393 if (set_size(context->connections) > CONNECTIONS_MAX) {
394 log_warning("Hit connection limit, refusing connection.");
399 r = set_ensure_allocated(&context->connections, trivial_hash_func, trivial_compare_func);
403 c = new0(Connection, 1);
407 c->context = context;
410 c->server_to_client_buffer[0] = c->server_to_client_buffer[1] = -1;
411 c->client_to_server_buffer[0] = c->client_to_server_buffer[1] = -1;
413 r = set_put(context->connections, c);
419 r = get_remote_sockaddr(&sa, &salen);
423 c->client_fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
424 if (c->client_fd < 0) {
425 log_error("Failed to get remote socket: %m");
429 r = connect(c->client_fd, &sa.sa, salen);
431 if (errno == EINPROGRESS) {
432 r = sd_event_add_io(event, &c->client_event_source, c->client_fd, EPOLLOUT, connect_cb, c);
434 log_error("Failed to add connection socket: %s", strerror(-r));
438 r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT);
440 log_error("Failed to enable oneshot event source: %s", strerror(-r));
444 log_error("Failed to connect to remote host: %m");
448 r = connection_enable_event_sources(c, event);
457 return 0; /* ignore non-OOM errors, continue serving */
460 static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
461 _cleanup_free_ char *peer = NULL;
462 Context *context = userdata;
467 assert(revents & EPOLLIN);
470 nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
472 if (errno != -EAGAIN)
473 log_warning("Failed to accept() socket: %m");
475 getpeername_pretty(nfd, &peer);
476 log_debug("New connection from %s", strna(peer));
478 r = add_connection_socket(context, sd_event_source_get_event(s), nfd);
480 log_error("Failed to accept connection, ignoring: %s", strerror(-r));
485 r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
487 log_error("Error while re-enabling listener with ONESHOT: %s", strerror(-r));
488 sd_event_exit(sd_event_source_get_event(s), r);
495 static int add_listen_socket(Context *context, sd_event *event, int fd) {
496 sd_event_source *source;
503 r = set_ensure_allocated(&context->listen, trivial_hash_func, trivial_compare_func);
509 r = sd_is_socket(fd, 0, SOCK_STREAM, 1);
511 log_error("Failed to determine socket type: %s", strerror(-r));
515 log_error("Passed in socket is not a stream socket.");
519 r = fd_nonblock(fd, true);
521 log_error("Failed to mark file descriptor non-blocking: %s", strerror(-r));
525 r = sd_event_add_io(event, &source, fd, EPOLLIN, accept_cb, context);
527 log_error("Failed to add event source: %s", strerror(-r));
531 r = set_put(context->listen, source);
533 log_error("Failed to add source to set: %s", strerror(-r));
534 sd_event_source_unref(source);
538 /* Set the watcher to oneshot in case other processes are also
539 * watching to accept(). */
540 r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
542 log_error("Failed to enable oneshot mode: %s", strerror(-r));
549 static int help(void) {
551 printf("%s [HOST:PORT]\n"
553 "Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
554 " -h --help Show this help\n"
555 " --version Show package version\n",
556 program_invocation_short_name,
557 program_invocation_short_name);
562 static int parse_argv(int argc, char *argv[]) {
569 static const struct option options[] = {
570 { "help", no_argument, NULL, 'h' },
571 { "version", no_argument, NULL, ARG_VERSION },
580 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
588 puts(PACKAGE_STRING);
589 puts(SYSTEMD_FEATURES);
596 assert_not_reached("Unhandled option");
600 if (optind >= argc) {
601 log_error("Not enough parameters.");
605 if (argc != optind+1) {
606 log_error("Too many parameters.");
610 arg_remote_host = argv[optind];
614 int main(int argc, char *argv[]) {
615 _cleanup_event_unref_ sd_event *event = NULL;
616 Context context = {};
619 log_parse_environment();
622 r = parse_argv(argc, argv);
626 r = sd_event_default(&event);
628 log_error("Failed to allocate event loop: %s", strerror(-r));
632 sd_event_set_watchdog(event, true);
634 n = sd_listen_fds(1);
636 log_error("Failed to receive sockets from parent.");
640 log_error("Didn't get any sockets passed in.");
645 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
646 r = add_listen_socket(&context, event, fd);
651 r = sd_event_loop(event);
653 log_error("Failed to run event loop: %s", strerror(-r));
658 context_free(&context);
660 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;