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 {
56 int server_fd, client_fd;
57 int server_to_client_buffer[2]; /* a pipe */
58 int client_to_server_buffer[2]; /* a pipe */
60 size_t server_to_client_buffer_full, client_to_server_buffer_full;
61 size_t server_to_client_buffer_size, client_to_server_buffer_size;
63 sd_event_source *server_event_source, *client_event_source;
66 static const char *arg_remote_host = NULL;
68 static void connection_free(Connection *c) {
71 sd_event_source_unref(c->server_event_source);
72 sd_event_source_unref(c->client_event_source);
74 if (c->server_fd >= 0)
75 close_nointr_nofail(c->server_fd);
76 if (c->client_fd >= 0)
77 close_nointr_nofail(c->client_fd);
79 close_pipe(c->server_to_client_buffer);
80 close_pipe(c->client_to_server_buffer);
85 static void context_free(Context *context) {
91 while ((es = set_steal_first(context->listen)))
92 sd_event_source_unref(es);
94 while ((c = set_steal_first(context->connections)))
97 set_free(context->listen);
98 set_free(context->connections);
101 static int get_remote_sockaddr(union sockaddr_union *sa, socklen_t *salen) {
107 if (path_is_absolute(arg_remote_host)) {
108 sa->un.sun_family = AF_UNIX;
109 strncpy(sa->un.sun_path, arg_remote_host, sizeof(sa->un.sun_path)-1);
110 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
112 *salen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa->un.sun_path);
114 } else if (arg_remote_host[0] == '@') {
115 sa->un.sun_family = AF_UNIX;
116 sa->un.sun_path[0] = 0;
117 strncpy(sa->un.sun_path+1, arg_remote_host+1, sizeof(sa->un.sun_path)-2);
118 sa->un.sun_path[sizeof(sa->un.sun_path)-1] = 0;
120 *salen = offsetof(union sockaddr_union, un.sun_path) + 1 + strlen(sa->un.sun_path + 1);
123 _cleanup_freeaddrinfo_ struct addrinfo *result = NULL;
124 const char *node, *service;
126 struct addrinfo hints = {
127 .ai_family = AF_UNSPEC,
128 .ai_socktype = SOCK_STREAM,
129 .ai_flags = AI_ADDRCONFIG
132 service = strrchr(arg_remote_host, ':');
134 node = strndupa(arg_remote_host, service - arg_remote_host);
137 node = arg_remote_host;
141 log_debug("Looking up address info for %s:%s", node, service);
142 r = getaddrinfo(node, service, &hints, &result);
144 log_error("Failed to resolve host %s:%s: %s", node, service, gai_strerror(r));
145 return -EHOSTUNREACH;
149 if (result->ai_addrlen > sizeof(union sockaddr_union)) {
150 log_error("Address too long.");
154 memcpy(sa, result->ai_addr, result->ai_addrlen);
155 *salen = result->ai_addrlen;
161 static int connection_create_pipes(Connection *c, int buffer[2], size_t *sz) {
171 r = pipe2(buffer, O_CLOEXEC|O_NONBLOCK);
173 log_error("Failed to allocate pipe buffer: %m");
177 fcntl(buffer[0], F_SETPIPE_SZ, BUFFER_SIZE);
179 r = fcntl(buffer[0], F_GETPIPE_SZ);
181 log_error("Failed to get pipe buffer size: %m");
191 static int connection_shovel(
193 int *from, int buffer[2], int *to,
194 size_t *full, size_t *sz,
195 sd_event_source **from_source, sd_event_source **to_source) {
202 assert(buffer[0] >= 0);
203 assert(buffer[1] >= 0);
215 if (*full < *sz && *from >= 0 && *to >= 0) {
216 z = splice(*from, NULL, buffer[1], NULL, *sz - *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
220 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
221 *from_source = sd_event_source_unref(*from_source);
222 close_nointr_nofail(*from);
224 } else if (errno != EAGAIN && errno != EINTR) {
225 log_error("Failed to splice: %m");
230 if (*full > 0 && *to >= 0) {
231 z = splice(buffer[0], NULL, *to, NULL, *full, SPLICE_F_MOVE|SPLICE_F_NONBLOCK);
235 } else if (z == 0 || errno == EPIPE || errno == ECONNRESET) {
236 *to_source = sd_event_source_unref(*to_source);
237 close_nointr_nofail(*to);
239 } else if (errno != EAGAIN && errno != EINTR) {
240 log_error("Failed to splice: %m");
249 static int connection_enable_event_sources(Connection *c, sd_event *event);
251 static int traffic_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
252 Connection *c = userdata;
259 r = connection_shovel(c,
260 &c->server_fd, c->server_to_client_buffer, &c->client_fd,
261 &c->server_to_client_buffer_full, &c->server_to_client_buffer_size,
262 &c->server_event_source, &c->client_event_source);
266 r = connection_shovel(c,
267 &c->client_fd, c->client_to_server_buffer, &c->server_fd,
268 &c->client_to_server_buffer_full, &c->client_to_server_buffer_size,
269 &c->client_event_source, &c->server_event_source);
273 /* EOF on both sides? */
274 if (c->server_fd == -1 && c->client_fd == -1)
277 /* Server closed, and all data written to client? */
278 if (c->server_fd == -1 && c->server_to_client_buffer_full <= 0)
281 /* Client closed, and all data written to server? */
282 if (c->client_fd == -1 && c->client_to_server_buffer_full <= 0)
285 r = connection_enable_event_sources(c, sd_event_get(s));
293 return 0; /* ignore errors, continue serving */
296 static int connection_enable_event_sources(Connection *c, sd_event *event) {
297 uint32_t a = 0, b = 0;
303 if (c->server_to_client_buffer_full > 0)
305 if (c->server_to_client_buffer_full < c->server_to_client_buffer_size)
308 if (c->client_to_server_buffer_full > 0)
310 if (c->client_to_server_buffer_full < c->client_to_server_buffer_size)
313 if (c->server_event_source)
314 r = sd_event_source_set_io_events(c->server_event_source, a);
315 else if (c->server_fd >= 0)
316 r = sd_event_add_io(event, c->server_fd, a, traffic_cb, c, &c->server_event_source);
321 log_error("Failed to set up server event source: %s", strerror(-r));
325 if (c->client_event_source)
326 r = sd_event_source_set_io_events(c->client_event_source, b);
327 else if (c->client_fd >= 0)
328 r = sd_event_add_io(event, c->client_fd, b, traffic_cb, c, &c->client_event_source);
333 log_error("Failed to set up client event source: %s", strerror(-r));
340 static int connect_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
341 Connection *c = userdata;
349 solen = sizeof(error);
350 r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &solen);
352 log_error("Failed to issue SO_ERROR: %m");
357 log_error("Failed to connect to remote host: %s", strerror(error));
361 c->client_event_source = sd_event_source_unref(c->client_event_source);
363 r = connection_create_pipes(c, c->server_to_client_buffer, &c->server_to_client_buffer_size);
367 r = connection_create_pipes(c, c->client_to_server_buffer, &c->client_to_server_buffer_size);
371 r = connection_enable_event_sources(c, sd_event_get(s));
379 return 0; /* ignore errors, continue serving */
382 static int add_connection_socket(Context *context, sd_event *event, int fd) {
383 union sockaddr_union sa = {};
392 if (set_size(context->connections) > CONNECTIONS_MAX) {
393 log_warning("Hit connection limit, refusing connection.");
394 close_nointr_nofail(fd);
398 r = set_ensure_allocated(&context->connections, trivial_hash_func, trivial_compare_func);
402 c = new0(Connection, 1);
408 c->server_to_client_buffer[0] = c->server_to_client_buffer[1] = -1;
409 c->client_to_server_buffer[0] = c->client_to_server_buffer[1] = -1;
411 r = get_remote_sockaddr(&sa, &salen);
415 c->client_fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
416 if (c->client_fd < 0) {
417 log_error("Failed to get remote socket: %m");
421 r = connect(c->client_fd, &sa.sa, salen);
423 if (errno == EINPROGRESS) {
424 r = sd_event_add_io(event, c->client_fd, EPOLLOUT, connect_cb, c, &c->client_event_source);
426 log_error("Failed to add connection socket: %s", strerror(-r));
430 r = sd_event_source_set_enabled(c->client_event_source, SD_EVENT_ONESHOT);
432 log_error("Failed to enable oneshot event source: %s", strerror(-r));
436 log_error("Failed to connect to remote host: %m");
440 r = connection_enable_event_sources(c, event);
449 return 0; /* ignore non-OOM errors, continue serving */
452 static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
453 Context *context = userdata;
458 assert(revents & EPOLLIN);
461 nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
463 _cleanup_free_ char *peer = NULL;
465 getpeername_pretty(nfd, &peer);
466 log_debug("New connection from %s", strna(peer));
468 r = add_connection_socket(context, sd_event_get(s), nfd);
470 close_nointr_nofail(fd);
474 } else if (errno != -EAGAIN)
475 log_warning("Failed to accept() socket: %m");
477 r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
479 log_error("Error %d while re-enabling listener with ONESHOT: %s", r, strerror(-r));
486 static int add_listen_socket(Context *context, sd_event *event, int fd) {
487 sd_event_source *source;
494 log_info("Listening on %i", fd);
496 r = set_ensure_allocated(&context->listen, trivial_hash_func, trivial_compare_func);
502 r = sd_is_socket(fd, 0, SOCK_STREAM, 1);
504 log_error("Failed to determine socket type: %s", strerror(-r));
508 log_error("Passed in socket is not a stream socket.");
512 r = fd_nonblock(fd, true);
514 log_error("Failed to mark file descriptor non-blocking: %s", strerror(-r));
518 r = sd_event_add_io(event, fd, EPOLLIN, accept_cb, context, &source);
520 log_error("Failed to add event source: %s", strerror(-r));
524 r = set_put(context->listen, source);
526 log_error("Failed to add source to set: %s", strerror(-r));
527 sd_event_source_unref(source);
531 /* Set the watcher to oneshot in case other processes are also
532 * watching to accept(). */
533 r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
535 log_error("Failed to enable oneshot mode: %s", strerror(-r));
542 static int help(void) {
544 printf("%s [HOST:PORT]\n"
546 "Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
547 " -h --help Show this help\n"
548 " --version Show package version\n",
549 program_invocation_short_name,
550 program_invocation_short_name);
555 static int parse_argv(int argc, char *argv[]) {
562 static const struct option options[] = {
563 { "help", no_argument, NULL, 'h' },
564 { "version", no_argument, NULL, ARG_VERSION },
573 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
581 puts(PACKAGE_STRING);
582 puts(SYSTEMD_FEATURES);
589 assert_not_reached("Unhandled option");
593 if (optind >= argc) {
594 log_error("Not enough parameters.");
598 if (argc != optind+1) {
599 log_error("Too many parameters.");
603 arg_remote_host = argv[optind];
607 int main(int argc, char *argv[]) {
608 _cleanup_event_unref_ sd_event *event = NULL;
609 Context context = {};
612 log_parse_environment();
615 r = parse_argv(argc, argv);
619 r = sd_event_new(&event);
621 log_error("Failed to allocate event loop: %s", strerror(-r));
625 n = sd_listen_fds(1);
627 log_error("Failed to receive sockets from parent.");
631 log_error("Didn't get any sockets passed in.");
636 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
637 r = add_listen_socket(&context, event, fd);
642 r = sd_event_loop(event);
644 log_error("Failed to run event loop: %s", strerror(-r));
649 context_free(&context);
651 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;