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 if (c->server_fd >= 0)
80 close_nointr_nofail(c->server_fd);
81 if (c->client_fd >= 0)
82 close_nointr_nofail(c->client_fd);
84 close_pipe(c->server_to_client_buffer);
85 close_pipe(c->client_to_server_buffer);
90 static void context_free(Context *context) {
96 while ((es = set_steal_first(context->listen)))
97 sd_event_source_unref(es);
99 while ((c = set_first(context->connections)))
102 set_free(context->listen);
103 set_free(context->connections);
106 static int get_remote_sockaddr(union sockaddr_union *sa, socklen_t *salen) {
112 if (path_is_absolute(arg_remote_host)) {
113 sa->un.sun_family = AF_UNIX;
114 strncpy(sa->un.sun_path, arg_remote_host, sizeof(sa->un.sun_path)-1);
115 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
117 *salen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa->un.sun_path);
119 } else if (arg_remote_host[0] == '@') {
120 sa->un.sun_family = AF_UNIX;
121 sa->un.sun_path[0] = 0;
122 strncpy(sa->un.sun_path+1, arg_remote_host+1, sizeof(sa->un.sun_path)-2);
123 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
125 *salen = offsetof(union sockaddr_union, un.sun_path) + 1 + strlen(sa->un.sun_path + 1);
128 _cleanup_freeaddrinfo_ struct addrinfo *result = NULL;
129 const char *node, *service;
131 struct addrinfo hints = {
132 .ai_family = AF_UNSPEC,
133 .ai_socktype = SOCK_STREAM,
134 .ai_flags = AI_ADDRCONFIG
137 service = strrchr(arg_remote_host, ':');
139 node = strndupa(arg_remote_host, service - arg_remote_host);
142 node = arg_remote_host;
146 log_debug("Looking up address info for %s:%s", node, service);
147 r = getaddrinfo(node, service, &hints, &result);
149 log_error("Failed to resolve host %s:%s: %s", node, service, gai_strerror(r));
150 return -EHOSTUNREACH;
154 if (result->ai_addrlen > sizeof(union sockaddr_union)) {
155 log_error("Address too long.");
159 memcpy(sa, result->ai_addr, result->ai_addrlen);
160 *salen = result->ai_addrlen;
166 static int connection_create_pipes(Connection *c, int buffer[2], size_t *sz) {
176 r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK);
178 log_error("Failed to allocate pipe buffer: %m");
182 fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE);
184 r = fcntl(buffer[0], F_GETPIPE_SZ);
186 log_error("Failed to get pipe buffer size: %m");
196 static int connection_shovel(
198 int *from, int buffer[2], int *to,
199 size_t *full, size_t *sz,
200 sd_event_source **from_source, sd_event_source **to_source) {
207 assert(buffer[0] >= 0);
208 assert(buffer[1] >= 0);
220 if (*full < *sz && *from >= 0 && *to >= 0) {
221 z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
225 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
226 *from_source = sd_event_source_unref(*from_source);
227 close_nointr_nofail(*from);
229 } else if (errno != EAGAIN && errno != EINTR) {
230 log_error("Failed to splice: %m");
235 if (*full > 0 && *to >= 0) {
236 z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
240 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
241 *to_source = sd_event_source_unref(*to_source);
242 close_nointr_nofail(*to);
244 } else if (errno != EAGAIN && errno != EINTR) {
245 log_error("Failed to splice: %m");
254 static int connection_enable_event_sources(Connection *c, sd_event *event);
256 static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
257 Connection *c = userdata;
264 r = connection_shovel(c,
265 &c->server_fd, c->server_to_client_buffer, &c->client_fd,
266 &c->server_to_client_buffer_full, &c->server_to_client_buffer_size,
267 &c->server_event_source, &c->client_event_source);
271 r = connection_shovel(c,
272 &c->client_fd, c->client_to_server_buffer, &c->server_fd,
273 &c->client_to_server_buffer_full, &c->client_to_server_buffer_size,
274 &c->client_event_source, &c->server_event_source);
278 /* EOF on both sides? */
279 if (c->server_fd == -1 && c->client_fd == -1)
282 /* Server closed, and all data written to client? */
283 if (c->server_fd == -1 && c->server_to_client_buffer_full <= 0)
286 /* Client closed, and all data written to server? */
287 if (c->client_fd == -1 && c->client_to_server_buffer_full <= 0)
290 r = connection_enable_event_sources(c, sd_event_source_get_event(s));
298 return 0; /* ignore errors, continue serving */
301 static int connection_enable_event_sources(Connection *c, sd_event *event) {
302 uint32_t a = 0, b = 0;
308 if (c->server_to_client_buffer_full > 0)
310 if (c->server_to_client_buffer_full < c->server_to_client_buffer_size)
313 if (c->client_to_server_buffer_full > 0)
315 if (c->client_to_server_buffer_full < c->client_to_server_buffer_size)
318 if (c->server_event_source)
319 r = sd_event_source_set_io_events(c->server_event_source, a);
320 else if (c->server_fd >= 0)
321 r = sd_event_add_io(event, c->server_fd, a, traffic_cb, c, &c->server_event_source);
326 log_error("Failed to set up server event source: %s", strerror(-r));
330 if (c->client_event_source)
331 r = sd_event_source_set_io_events(c->client_event_source, b);
332 else if (c->client_fd >= 0)
333 r = sd_event_add_io(event, c->client_fd, b, traffic_cb, c, &c->client_event_source);
338 log_error("Failed to set up client event source: %s", strerror(-r));
345 static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
346 Connection *c = userdata;
354 solen = sizeof(error);
355 r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &solen);
357 log_error("Failed to issue SO_ERROR: %m");
362 log_error("Failed to connect to remote host: %s", strerror(error));
366 c->client_event_source = sd_event_source_unref(c->client_event_source);
368 r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size);
372 r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size);
376 r = connection_enable_event_sources(c, sd_event_source_get_event(s));
384 return 0; /* ignore errors, continue serving */
387 static int add_connection_socket(Context *context, sd_event *event, int fd) {
388 union sockaddr_union sa = {};
397 if (set_size(context->connections) > CONNECTIONS_MAX) {
398 log_warning("Hit connection limit, refusing connection.");
399 close_nointr_nofail(fd);
403 r = set_ensure_allocated(&context->connections, trivial_hash_func, trivial_compare_func);
407 c = new0(Connection, 1);
411 c->context = context;
414 c->server_to_client_buffer[0] = c->server_to_client_buffer[1] = -1;
415 c->client_to_server_buffer[0] = c->client_to_server_buffer[1] = -1;
417 r = set_put(context->connections, c);
423 r = get_remote_sockaddr(&sa, &salen);
427 c->client_fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
428 if (c->client_fd < 0) {
429 log_error("Failed to get remote socket: %m");
433 r = connect(c->client_fd, &sa.sa, salen);
435 if (errno == EINPROGRESS) {
436 r = sd_event_add_io(event, c->client_fd, EPOLLOUT, connect_cb, c, &c->client_event_source);
438 log_error("Failed to add connection socket: %s", strerror(-r));
442 r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT);
444 log_error("Failed to enable oneshot event source: %s", strerror(-r));
448 log_error("Failed to connect to remote host: %m");
452 r = connection_enable_event_sources(c, event);
461 return 0; /* ignore non-OOM errors, continue serving */
464 static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
465 _cleanup_free_ char *peer = NULL;
466 Context *context = userdata;
471 assert(revents & EPOLLIN);
474 nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
476 if (errno != -EAGAIN)
477 log_warning("Failed to accept() socket: %m");
479 getpeername_pretty(nfd, &peer);
480 log_debug("New connection from %s", strna(peer));
482 r = add_connection_socket(context, sd_event_source_get_event(s), nfd);
484 log_error("Failed to accept connection, ignoring: %s", strerror(-r));
485 close_nointr_nofail(fd);
489 r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
491 log_error("Error while re-enabling listener with ONESHOT: %s", strerror(-r));
492 sd_event_exit(sd_event_source_get_event(s), r);
499 static int add_listen_socket(Context *context, sd_event *event, int fd) {
500 sd_event_source *source;
507 r = set_ensure_allocated(&context->listen, trivial_hash_func, trivial_compare_func);
513 r = sd_is_socket(fd, 0, SOCK_STREAM, 1);
515 log_error("Failed to determine socket type: %s", strerror(-r));
519 log_error("Passed in socket is not a stream socket.");
523 r = fd_nonblock(fd, true);
525 log_error("Failed to mark file descriptor non-blocking: %s", strerror(-r));
529 r = sd_event_add_io(event, fd, EPOLLIN, accept_cb, context, &source);
531 log_error("Failed to add event source: %s", strerror(-r));
535 r = set_put(context->listen, source);
537 log_error("Failed to add source to set: %s", strerror(-r));
538 sd_event_source_unref(source);
542 /* Set the watcher to oneshot in case other processes are also
543 * watching to accept(). */
544 r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
546 log_error("Failed to enable oneshot mode: %s", strerror(-r));
553 static int help(void) {
555 printf("%s [HOST:PORT]\n"
557 "Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
558 " -h --help Show this help\n"
559 " --version Show package version\n",
560 program_invocation_short_name,
561 program_invocation_short_name);
566 static int parse_argv(int argc, char *argv[]) {
573 static const struct option options[] = {
574 { "help", no_argument, NULL, 'h' },
575 { "version", no_argument, NULL, ARG_VERSION },
584 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
592 puts(PACKAGE_STRING);
593 puts(SYSTEMD_FEATURES);
600 assert_not_reached("Unhandled option");
604 if (optind >= argc) {
605 log_error("Not enough parameters.");
609 if (argc != optind+1) {
610 log_error("Too many parameters.");
614 arg_remote_host = argv[optind];
618 int main(int argc, char *argv[]) {
619 _cleanup_event_unref_ sd_event *event = NULL;
620 Context context = {};
623 log_parse_environment();
626 r = parse_argv(argc, argv);
630 r = sd_event_default(&event);
632 log_error("Failed to allocate event loop: %s", strerror(-r));
636 sd_event_set_watchdog(event, true);
638 n = sd_listen_fds(1);
640 log_error("Failed to receive sockets from parent.");
644 log_error("Didn't get any sockets passed in.");
649 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
650 r = add_listen_socket(&context, event, fd);
655 r = sd_event_loop(event);
657 log_error("Failed to run event loop: %s", strerror(-r));
662 context_free(&context);
664 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;