From d1b38fac57d82f0249b9e581eb0d18175f6aa74a Mon Sep 17 00:00:00 2001 From: David Strauss Date: Tue, 15 Oct 2013 17:00:18 -0700 Subject: Rename sabridge to saproxy to be less cryptic --- .gitignore | 2 +- Makefile-man.am | 2 +- Makefile.am | 10 +- TODO | 8 +- man/systemd-sabridge.xml | 254 --------------------- man/systemd-saproxy.xml | 254 +++++++++++++++++++++ src/sabridge/Makefile | 28 --- src/sabridge/sabridge.c | 575 ----------------------------------------------- src/saproxy/Makefile | 28 +++ src/saproxy/saproxy.c | 575 +++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 868 insertions(+), 868 deletions(-) delete mode 100644 man/systemd-sabridge.xml create mode 100644 man/systemd-saproxy.xml delete mode 100644 src/sabridge/Makefile delete mode 100644 src/sabridge/sabridge.c create mode 100644 src/saproxy/Makefile create mode 100644 src/saproxy/saproxy.c diff --git a/.gitignore b/.gitignore index d2d5da58f5..22485b42d6 100644 --- a/.gitignore +++ b/.gitignore @@ -71,7 +71,7 @@ /systemd-reply-password /systemd-rfkill /systemd-run -/systemd-sabridge +/systemd-saproxy /systemd-shutdown /systemd-shutdownd /systemd-sleep diff --git a/Makefile-man.am b/Makefile-man.am index e78a8a21e6..6b286bc48e 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -66,7 +66,7 @@ MANPAGES += \ man/systemd-nspawn.1 \ man/systemd-remount-fs.service.8 \ man/systemd-run.1 \ - man/systemd-sabridge.1 \ + man/systemd-saproxy.1 \ man/systemd-shutdownd.service.8 \ man/systemd-sleep.conf.5 \ man/systemd-suspend.service.8 \ diff --git a/Makefile.am b/Makefile.am index 287718413b..59c5174268 100644 --- a/Makefile.am +++ b/Makefile.am @@ -300,7 +300,7 @@ bin_PROGRAMS = \ systemd-delta \ systemd-analyze \ systemd-run \ - systemd-sabridge + systemd-saproxy dist_bin_SCRIPTS = \ src/kernel-install/kernel-install @@ -3210,10 +3210,10 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ -systemd_sabridge_SOURCES = \ - src/sabridge/sabridge.c +systemd_saproxy_SOURCES = \ + src/saproxy/saproxy.c -systemd_sabridge_LDADD = \ +systemd_saproxy_LDADD = \ libsystemd-shared.la \ libsystemd-logs.la \ libsystemd-journal-internal.la \ @@ -3221,7 +3221,7 @@ systemd_sabridge_LDADD = \ libsystemd-daemon.la \ libsystemd-bus.la -systemd_sabridge_CFLAGS = \ +systemd_saproxy_CFLAGS = \ $(AM_CFLAGS) # ------------------------------------------------------------------------------ diff --git a/TODO b/TODO index a047f626e2..a3ab09e504 100644 --- a/TODO +++ b/TODO @@ -48,13 +48,13 @@ CGroup Rework Completion: Features: -* sabridge: Support multiple inherited sockets mapped using different proxies +* saproxy: Support multiple inherited sockets mapped using different proxies -* sabridge: Use a nonblocking alternative to getaddrinfo +* saproxy: Use a nonblocking alternative to getaddrinfo -* sabridge: Until we can start daemons directly, find a less ugly, less racy alternative than shell scripts for the second man page example. +* saproxy: Until we can start daemons directly, find a less ugly, less racy alternative than shell scripts for the second man page example. -* sabridge: Support starting daemons directly without requiring a shell script; update man pages +* saproxy: Support starting daemons directly without requiring a shell script; update man pages * "systemctl cat" or "systemctl view" command or or so, that cats the backing unit file of a service, plus its drop-ins and shows them in a pager diff --git a/man/systemd-sabridge.xml b/man/systemd-sabridge.xml deleted file mode 100644 index abeb1a5a4c..0000000000 --- a/man/systemd-sabridge.xml +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - systemd-sabridge - systemd - - - Developer - David - Strauss - david@davidstrauss.net - - - - - systemd-sabridge - 1 - - - systemd-sabridge - Inherit a socket. Bidirectionally - proxy. - - - - systemd-sabridge - OPTIONS - HOSTNAME-OR-IP - PORT-OR-SERVICE - - - systemd-sabridge - OPTIONS - UNIX-DOMAIN-SOCKET-PATH - - - - - Description - - systemd-sabridgeprovides a proxy - to socket-activate services that do not yet support - native socket activation. On behalf of the daemon, - the proxy inherits the socket from systemd, accepts - each client connection, opens a connection to the server - for each client, and then bidirectionally forwards - data between the two. - This utility's behavior is similar to - socat1 . - The main differences for systemd-sabridge - are support for socket activation with - Accept=false and an event-driven - design that scales better with the number of - connections. - - - Options - The following options are understood: - - - - - - Prints a short help - text and exits. - - - - - - Prints a version - string and exits. - - - - - - Skips verification of - the expected PID and file - descriptor numbers. Use if - invoked indirectly, for - example with a shell script - rather than with - - - - - - - - Exit status - On success 0 is returned, a non-zero failure - code otherwise. - - - Examples - - Direct-Use Example - Use two services with a dependency - and no namespace isolation. - - /etc/systemd/system/bridge-to-nginx.socket - - - - - - /etc/systemd/system/bridge-to-nginx.service - - - - - - /etc/nginx/nginx.conf - - - - - - - - - - - - Indirect-Use Example - Use a shell script to isolate the - service and bridge into the same namespace. - This is particularly useful for running - TCP-only daemons without the daemon - affecting ports on regular - interfaces. - - - - /etc/systemd/system/bridge-with-nginx.socket - - - - - - - - /etc/systemd/system/bridge-with-nginx.service - - - - - - - /usr/bin/sabridge-nginx.sh - - - - - - - /etc/nginx/nginx.conf - - - - - - - - - - - - - See Also - - - - systemd.service - 5 - , - - - systemd.socket - 5 - , - - systemctl - 1 - , - - socat - 1 - - - diff --git a/man/systemd-saproxy.xml b/man/systemd-saproxy.xml new file mode 100644 index 0000000000..1314b31734 --- /dev/null +++ b/man/systemd-saproxy.xml @@ -0,0 +1,254 @@ + + + + + + + systemd-saproxy + systemd + + + Developer + David + Strauss + david@davidstrauss.net + + + + + systemd-saproxy + 1 + + + systemd-saproxy + Inherit a socket. Bidirectionally + proxy. + + + + systemd-saproxy + OPTIONS + HOSTNAME-OR-IP + PORT-OR-SERVICE + + + systemd-saproxy + OPTIONS + UNIX-DOMAIN-SOCKET-PATH + + + + + Description + + systemd-saproxyprovides a proxy + to socket-activate services that do not yet support + native socket activation. On behalf of the daemon, + the proxy inherits the socket from systemd, accepts + each client connection, opens a connection to the server + for each client, and then bidirectionally forwards + data between the two. + This utility's behavior is similar to + socat1 . + The main differences for systemd-saproxy + are support for socket activation with + Accept=false and an event-driven + design that scales better with the number of + connections. + + + Options + The following options are understood: + + + + + + Prints a short help + text and exits. + + + + + + Prints a version + string and exits. + + + + + + Skips verification of + the expected PID and file + descriptor numbers. Use if + invoked indirectly, for + example with a shell script + rather than with + + + + + + + + Exit status + On success 0 is returned, a non-zero failure + code otherwise. + + + Examples + + Direct-Use Example + Use two services with a dependency + and no namespace isolation. + + /etc/systemd/system/bridge-to-nginx.socket + + + + + + /etc/systemd/system/bridge-to-nginx.service + + + + + + /etc/nginx/nginx.conf + + + + + + + + + + + + Indirect-Use Example + Use a shell script to isolate the + service and bridge into the same namespace. + This is particularly useful for running + TCP-only daemons without the daemon + affecting ports on regular + interfaces. + + + + /etc/systemd/system/bridge-with-nginx.socket + + + + + + + + /etc/systemd/system/bridge-with-nginx.service + + + + + + + /usr/bin/saproxy-nginx.sh + + + + + + + /etc/nginx/nginx.conf + + + + + + + + + + + + + See Also + + + + systemd.service + 5 + , + + + systemd.socket + 5 + , + + systemctl + 1 + , + + socat + 1 + + + diff --git a/src/sabridge/Makefile b/src/sabridge/Makefile deleted file mode 100644 index 9d07505194..0000000000 --- a/src/sabridge/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2010 Lennart Poettering -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# systemd 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with systemd; If not, see . - -# This file is a dirty trick to simplify compilation from within -# emacs. This file is not intended to be distributed. So, don't touch -# it, even better ignore it! - -all: - $(MAKE) -C .. - -clean: - $(MAKE) -C .. clean - -.PHONY: all clean diff --git a/src/sabridge/sabridge.c b/src/sabridge/sabridge.c deleted file mode 100644 index 0589871870..0000000000 --- a/src/sabridge/sabridge.c +++ /dev/null @@ -1,575 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 David Strauss - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . - ***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "sd-daemon.h" -#include "sd-event.h" -#include "socket-util.h" -#include "util.h" - -#define BUFFER_SIZE 4096 -#define _cleanup_freeaddrinfo_ _cleanup_(freeaddrinfop) - -unsigned int total_clients = 0; - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct addrinfo *, freeaddrinfo); - -struct proxy { - int listen_fd; - bool ignore_env; - bool remote_is_inet; - const char *remote_host; - const char *remote_service; -}; - -struct connection { - int fd; - sd_event_source *w_recv; - sd_event_source *w_send; - struct connection *c_destination; - size_t buffer_filled_len; - size_t buffer_sent_len; - char buffer[BUFFER_SIZE]; -}; - -static void free_connection(struct connection *c) { - log_debug("Freeing fd=%d (conn %p).", c->fd, c); - sd_event_source_unref(c->w_recv); - sd_event_source_unref(c->w_send); - close(c->fd); - free(c); -} - -static int send_buffer(struct connection *sender) { - struct connection *receiver = sender->c_destination; - ssize_t len; - int r = 0; - - /* We cannot assume that even a partial send() indicates that - * the next send() will block. Loop until it does. */ - while (sender->buffer_filled_len > sender->buffer_sent_len) { - len = send(receiver->fd, sender->buffer + sender->buffer_sent_len, sender->buffer_filled_len - sender->buffer_sent_len, 0); - log_debug("send(%d, ...)=%ld", receiver->fd, len); - if (len < 0) { - if (errno != EWOULDBLOCK && errno != EAGAIN) { - log_error("Error %d in send to fd=%d: %m", errno, receiver->fd); - return -errno; - } - else { - /* send() is in a blocking state. */ - break; - } - } - - /* len < 0 can't occur here. len == 0 is possible but - * undefined behavior for nonblocking send(). */ - assert(len > 0); - sender->buffer_sent_len += len; - } - - log_debug("send(%d, ...) completed with %lu bytes still buffered.", receiver->fd, sender->buffer_filled_len - sender->buffer_sent_len); - - /* Detect a would-block state or partial send. */ - if (sender->buffer_filled_len > sender->buffer_sent_len) { - - /* If the buffer is full, disable events coming for recv. */ - if (sender->buffer_filled_len == BUFFER_SIZE) { - r = sd_event_source_set_enabled(sender->w_recv, SD_EVENT_OFF); - if (r < 0) { - log_error("Error %d disabling recv for fd=%d: %s", r, sender->fd, strerror(-r)); - return r; - } - } - - /* Watch for when the recipient can be sent data again. */ - r = sd_event_source_set_enabled(receiver->w_send, SD_EVENT_ON); - if (r < 0) { - log_error("Error %d enabling send for fd=%d: %s", r, receiver->fd, strerror(-r)); - return r; - } - log_debug("Done with recv for fd=%d. Waiting on send for fd=%d.", sender->fd, receiver->fd); - return r; - } - - /* If we sent everything without blocking, the buffer is now empty. */ - sender->buffer_filled_len = 0; - sender->buffer_sent_len = 0; - - /* Unmute the sender, in case the buffer was full. */ - r = sd_event_source_set_enabled(sender->w_recv, SD_EVENT_ON); - if (r < 0) { - log_error("Error %d enabling recv for fd=%d: %s", r, sender->fd, strerror(-r)); - return r; - } - - /* Mute the recipient, as we have no data to send now. */ - r = sd_event_source_set_enabled(receiver->w_send, SD_EVENT_OFF); - if (r < 0) { - log_error("Error %d disabling send for fd=%d: %s", r, receiver->fd, strerror(-r)); - return r; - } - - return 0; -} - -static int transfer_data_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - struct connection *c = (struct connection *) userdata; - int r = 0; - ssize_t len; - - assert(revents & (EPOLLIN | EPOLLOUT)); - assert(fd == c->fd); - - log_debug("Got event revents=%d from fd=%d (conn %p).", revents, fd, c); - - if (revents & EPOLLIN) { - log_debug("About to recv up to %lu bytes from fd=%d (%lu/BUFFER_SIZE).", BUFFER_SIZE - c->buffer_filled_len, fd, c->buffer_filled_len); - - assert(s == c->w_recv); - - /* Receive until the buffer's full, there's no more data, - * or the client/server disconnects. */ - while (c->buffer_filled_len < BUFFER_SIZE) { - len = recv(fd, c->buffer + c->buffer_filled_len, BUFFER_SIZE - c->buffer_filled_len, 0); - log_debug("recv(%d, ...)=%ld", fd, len); - if (len < 0) { - if (errno != EWOULDBLOCK && errno != EAGAIN) { - log_error("Error %d in recv from fd=%d: %m", errno, fd); - return -errno; - } - else { - /* recv() is in a blocking state. */ - break; - } - } - else if (len == 0) { - log_debug("Clean disconnection from fd=%d", fd); - total_clients--; - free_connection(c->c_destination); - free_connection(c); - return 0; - } - - assert(len > 0); - log_debug("Recording that the buffer got %ld more bytes full.", len); - c->buffer_filled_len += len; - log_debug("Buffer now has %ld bytes full.", c->buffer_filled_len); - } - - /* Try sending the data immediately. */ - return send_buffer(c); - } - else { - assert(s == c->w_send); - return send_buffer(c->c_destination); - } - - return r; -} - -/* Once sending to the server is unblocked, set up the real watchers. */ -static int connected_to_server_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - struct sd_event *e = NULL; - struct connection *c_server_to_client = (struct connection *) userdata; - struct connection *c_client_to_server = c_server_to_client->c_destination; - int r; - - assert(revents & EPOLLOUT); - - e = sd_event_get(s); - - /* Cancel the initial write watcher for the server. */ - sd_event_source_unref(s); - - log_debug("Connected to server. Initializing watchers for receiving data."); - - /* A disabled send watcher for the server. */ - r = sd_event_add_io(e, c_server_to_client->fd, EPOLLOUT, transfer_data_cb, c_server_to_client, &c_server_to_client->w_send); - if (r < 0) { - log_error("Error %d creating send watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); - goto fail; - } - r = sd_event_source_set_enabled(c_server_to_client->w_send, SD_EVENT_OFF); - if (r < 0) { - log_error("Error %d muting send for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); - goto finish; - } - - /* A recv watcher for the server. */ - r = sd_event_add_io(e, c_server_to_client->fd, EPOLLIN, transfer_data_cb, c_server_to_client, &c_server_to_client->w_recv); - if (r < 0) { - log_error("Error %d creating recv watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); - goto fail; - } - - /* A disabled send watcher for the client. */ - r = sd_event_add_io(e, c_client_to_server->fd, EPOLLOUT, transfer_data_cb, c_client_to_server, &c_client_to_server->w_send); - if (r < 0) { - log_error("Error %d creating send watcher for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); - goto fail; - } - r = sd_event_source_set_enabled(c_client_to_server->w_send, SD_EVENT_OFF); - if (r < 0) { - log_error("Error %d muting send for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); - goto finish; - } - - /* A recv watcher for the client. */ - r = sd_event_add_io(e, c_client_to_server->fd, EPOLLIN, transfer_data_cb, c_client_to_server, &c_client_to_server->w_recv); - if (r < 0) { - log_error("Error %d creating recv watcher for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); - goto fail; - } - -goto finish; - -fail: - free_connection(c_client_to_server); - free_connection(c_server_to_client); - -finish: - return r; -} - -static int get_server_connection_fd(const struct proxy *proxy) { - int server_fd; - int r = -EBADF; - int len; - - if (proxy->remote_is_inet) { - int s; - _cleanup_freeaddrinfo_ struct addrinfo *result = NULL; - struct addrinfo hints = {.ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_PASSIVE}; - - log_debug("Looking up address info for %s:%s", proxy->remote_host, proxy->remote_service); - s = getaddrinfo(proxy->remote_host, proxy->remote_service, &hints, &result); - if (s != 0) { - log_error("getaddrinfo error (%d): %s", s, gai_strerror(s)); - return r; - } - - if (result == NULL) { - log_error("getaddrinfo: no result"); - return r; - } - - /* @TODO: Try connecting to all results instead of just the first. */ - server_fd = socket(result->ai_family, result->ai_socktype | SOCK_NONBLOCK, result->ai_protocol); - if (server_fd < 0) { - log_error("Error %d creating socket: %m", errno); - return r; - } - - r = connect(server_fd, result->ai_addr, result->ai_addrlen); - /* Ignore EINPROGRESS errors because they're expected for a nonblocking socket. */ - if (r < 0 && errno != EINPROGRESS) { - log_error("Error %d while connecting to socket %s:%s: %m", errno, proxy->remote_host, proxy->remote_service); - return r; - } - } - else { - struct sockaddr_un remote; - - server_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (server_fd < 0) { - log_error("Error %d creating socket: %m", errno); - return -EBADFD; - } - - remote.sun_family = AF_UNIX; - strncpy(remote.sun_path, proxy->remote_host, sizeof(remote.sun_path)); - len = strlen(remote.sun_path) + sizeof(remote.sun_family); - r = connect(server_fd, (struct sockaddr *) &remote, len); - if (r < 0 && errno != EINPROGRESS) { - log_error("Error %d while connecting to Unix domain socket %s: %m", errno, proxy->remote_host); - return -EBADFD; - } - } - - log_debug("Server connection is fd=%d", server_fd); - return server_fd; -} - -static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - struct proxy *proxy = (struct proxy *) userdata; - struct connection *c_server_to_client; - struct connection *c_client_to_server; - int r = 0; - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - assert(revents & EPOLLIN); - - c_server_to_client = malloc0(sizeof(struct connection)); - if (c_server_to_client == NULL) { - log_oom(); - goto fail; - } - - c_client_to_server = malloc0(sizeof(struct connection)); - if (c_client_to_server == NULL) { - log_oom(); - goto fail; - } - - c_server_to_client->fd = get_server_connection_fd(proxy); - if (c_server_to_client->fd < 0) { - log_error("Error initiating server connection."); - goto fail; - } - - c_client_to_server->fd = accept(fd, (struct sockaddr *) &sa, &salen); - if (c_client_to_server->fd < 0) { - log_error("Error accepting client connection."); - goto fail; - } - - /* Unlike on BSD, client sockets do not inherit nonblocking status - * from the listening socket. */ - r = fd_nonblock(c_client_to_server->fd, true); - if (r < 0) { - log_error("Error %d marking client connection as nonblocking: %s", r, strerror(-r)); - goto fail; - } - - if (sa.sa.sa_family == AF_INET || sa.sa.sa_family == AF_INET6) { - char sa_str[INET6_ADDRSTRLEN]; - const char *success; - - success = inet_ntop(sa.sa.sa_family, &sa.in6.sin6_addr, sa_str, INET6_ADDRSTRLEN); - if (success == NULL) - log_warning("Error %d calling inet_ntop: %m", errno); - else - log_debug("Accepted client connection from %s as fd=%d", sa_str, c_client_to_server->fd); - } - else { - log_debug("Accepted client connection (non-IP) as fd=%d", c_client_to_server->fd); - } - - total_clients++; - log_debug("Client fd=%d (conn %p) successfully connected. Total clients: %u", c_client_to_server->fd, c_client_to_server, total_clients); - log_debug("Server fd=%d (conn %p) successfully initialized.", c_server_to_client->fd, c_server_to_client); - - /* Initialize watcher for send to server; this shows connectivity. */ - r = sd_event_add_io(sd_event_get(s), c_server_to_client->fd, EPOLLOUT, connected_to_server_cb, c_server_to_client, &c_server_to_client->w_send); - if (r < 0) { - log_error("Error %d creating connectivity watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); - goto fail; - } - - /* Allow lookups of the opposite connection. */ - c_server_to_client->c_destination = c_client_to_server; - c_client_to_server->c_destination = c_server_to_client; - - goto finish; - -fail: - log_warning("Accepting a client connection or connecting to the server failed."); - free_connection(c_client_to_server); - free_connection(c_server_to_client); - -finish: - /* Preserve the main loop even if a single proxy setup fails. */ - return 0; -} - -static int run_main_loop(struct proxy *proxy) { - int r = EXIT_SUCCESS; - struct sd_event *e = NULL; - sd_event_source *w_accept = NULL; - - r = sd_event_new(&e); - if (r < 0) - goto finish; - - r = fd_nonblock(proxy->listen_fd, true); - if (r < 0) - goto finish; - - log_debug("Initializing main listener fd=%d", proxy->listen_fd); - - sd_event_add_io(e, proxy->listen_fd, EPOLLIN, accept_cb, proxy, &w_accept); - - log_debug("Initialized main listener. Entering loop."); - - sd_event_loop(e); - -finish: - sd_event_source_unref(w_accept); - sd_event_unref(e); - - return r; -} - -static int help(void) { - - printf("%s hostname-or-ip port-or-service\n" - "%s unix-domain-socket-path\n\n" - "Inherit a socket. Bidirectionally proxy.\n\n" - " -h --help Show this help\n" - " --version Print version and exit\n" - " --ignore-env Ignore expected systemd environment\n", - program_invocation_short_name, - program_invocation_short_name); - - return 0; -} - -static void version(void) { - puts(PACKAGE_STRING " sabridge"); -} - -static int parse_argv(int argc, char *argv[], struct proxy *p) { - - enum { - ARG_VERSION = 0x100, - ARG_IGNORE_ENV - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "ignore-env", no_argument, NULL, ARG_IGNORE_ENV}, - { NULL, 0, NULL, 0 } - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { - - switch (c) { - - case 'h': - help(); - return 0; - - case '?': - return -EINVAL; - - case ARG_VERSION: - version(); - return 0; - - case ARG_IGNORE_ENV: - p->ignore_env = true; - continue; - - default: - log_error("Unknown option code %c", c); - return -EINVAL; - } - } - - if (optind + 1 != argc && optind + 2 != argc) { - log_error("Incorrect number of positional arguments."); - help(); - return -EINVAL; - } - - p->remote_host = argv[optind]; - assert(p->remote_host); - - p->remote_is_inet = p->remote_host[0] != '/'; - - if (optind == argc - 2) { - if (!p->remote_is_inet) { - log_error("A port or service is not allowed for Unix socket destinations."); - help(); - return -EINVAL; - } - p->remote_service = argv[optind + 1]; - assert(p->remote_service); - } else if (p->remote_is_inet) { - log_error("A port or service is required for IP destinations."); - help(); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - struct proxy p = {}; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv, &p); - if (r <= 0) - goto finish; - - p.listen_fd = SD_LISTEN_FDS_START; - - if (!p.ignore_env) { - int n; - n = sd_listen_fds(1); - if (n == 0) { - log_error("Found zero inheritable sockets. Are you sure this is running as a socket-activated service?"); - r = EXIT_FAILURE; - goto finish; - } else if (n < 0) { - log_error("Error %d while finding inheritable sockets: %s", n, strerror(-n)); - r = EXIT_FAILURE; - goto finish; - } else if (n > 1) { - log_error("Can't listen on more than one socket."); - r = EXIT_FAILURE; - goto finish; - } - } - - /* @TODO: Check if this proxy can work with datagram sockets. */ - r = sd_is_socket(p.listen_fd, 0, SOCK_STREAM, 1); - if (r < 0) { - log_error("Error %d while checking inherited socket: %s", r, strerror(-r)); - goto finish; - } - - log_info("Starting the socket activation bridge with listener fd=%d.", p.listen_fd); - - r = run_main_loop(&p); - if (r < 0) { - log_error("Error %d from main loop.", r); - goto finish; - } - -finish: - log_close(); - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/saproxy/Makefile b/src/saproxy/Makefile new file mode 100644 index 0000000000..9d07505194 --- /dev/null +++ b/src/saproxy/Makefile @@ -0,0 +1,28 @@ +# This file is part of systemd. +# +# Copyright 2010 Lennart Poettering +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +# This file is a dirty trick to simplify compilation from within +# emacs. This file is not intended to be distributed. So, don't touch +# it, even better ignore it! + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean + +.PHONY: all clean diff --git a/src/saproxy/saproxy.c b/src/saproxy/saproxy.c new file mode 100644 index 0000000000..ca75b400bf --- /dev/null +++ b/src/saproxy/saproxy.c @@ -0,0 +1,575 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 David Strauss + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . + ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "sd-daemon.h" +#include "sd-event.h" +#include "socket-util.h" +#include "util.h" + +#define BUFFER_SIZE 4096 +#define _cleanup_freeaddrinfo_ _cleanup_(freeaddrinfop) + +unsigned int total_clients = 0; + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct addrinfo *, freeaddrinfo); + +struct proxy { + int listen_fd; + bool ignore_env; + bool remote_is_inet; + const char *remote_host; + const char *remote_service; +}; + +struct connection { + int fd; + sd_event_source *w_recv; + sd_event_source *w_send; + struct connection *c_destination; + size_t buffer_filled_len; + size_t buffer_sent_len; + char buffer[BUFFER_SIZE]; +}; + +static void free_connection(struct connection *c) { + log_debug("Freeing fd=%d (conn %p).", c->fd, c); + sd_event_source_unref(c->w_recv); + sd_event_source_unref(c->w_send); + close(c->fd); + free(c); +} + +static int send_buffer(struct connection *sender) { + struct connection *receiver = sender->c_destination; + ssize_t len; + int r = 0; + + /* We cannot assume that even a partial send() indicates that + * the next send() will block. Loop until it does. */ + while (sender->buffer_filled_len > sender->buffer_sent_len) { + len = send(receiver->fd, sender->buffer + sender->buffer_sent_len, sender->buffer_filled_len - sender->buffer_sent_len, 0); + log_debug("send(%d, ...)=%ld", receiver->fd, len); + if (len < 0) { + if (errno != EWOULDBLOCK && errno != EAGAIN) { + log_error("Error %d in send to fd=%d: %m", errno, receiver->fd); + return -errno; + } + else { + /* send() is in a blocking state. */ + break; + } + } + + /* len < 0 can't occur here. len == 0 is possible but + * undefined behavior for nonblocking send(). */ + assert(len > 0); + sender->buffer_sent_len += len; + } + + log_debug("send(%d, ...) completed with %lu bytes still buffered.", receiver->fd, sender->buffer_filled_len - sender->buffer_sent_len); + + /* Detect a would-block state or partial send. */ + if (sender->buffer_filled_len > sender->buffer_sent_len) { + + /* If the buffer is full, disable events coming for recv. */ + if (sender->buffer_filled_len == BUFFER_SIZE) { + r = sd_event_source_set_enabled(sender->w_recv, SD_EVENT_OFF); + if (r < 0) { + log_error("Error %d disabling recv for fd=%d: %s", r, sender->fd, strerror(-r)); + return r; + } + } + + /* Watch for when the recipient can be sent data again. */ + r = sd_event_source_set_enabled(receiver->w_send, SD_EVENT_ON); + if (r < 0) { + log_error("Error %d enabling send for fd=%d: %s", r, receiver->fd, strerror(-r)); + return r; + } + log_debug("Done with recv for fd=%d. Waiting on send for fd=%d.", sender->fd, receiver->fd); + return r; + } + + /* If we sent everything without blocking, the buffer is now empty. */ + sender->buffer_filled_len = 0; + sender->buffer_sent_len = 0; + + /* Unmute the sender, in case the buffer was full. */ + r = sd_event_source_set_enabled(sender->w_recv, SD_EVENT_ON); + if (r < 0) { + log_error("Error %d enabling recv for fd=%d: %s", r, sender->fd, strerror(-r)); + return r; + } + + /* Mute the recipient, as we have no data to send now. */ + r = sd_event_source_set_enabled(receiver->w_send, SD_EVENT_OFF); + if (r < 0) { + log_error("Error %d disabling send for fd=%d: %s", r, receiver->fd, strerror(-r)); + return r; + } + + return 0; +} + +static int transfer_data_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct connection *c = (struct connection *) userdata; + int r = 0; + ssize_t len; + + assert(revents & (EPOLLIN | EPOLLOUT)); + assert(fd == c->fd); + + log_debug("Got event revents=%d from fd=%d (conn %p).", revents, fd, c); + + if (revents & EPOLLIN) { + log_debug("About to recv up to %lu bytes from fd=%d (%lu/BUFFER_SIZE).", BUFFER_SIZE - c->buffer_filled_len, fd, c->buffer_filled_len); + + assert(s == c->w_recv); + + /* Receive until the buffer's full, there's no more data, + * or the client/server disconnects. */ + while (c->buffer_filled_len < BUFFER_SIZE) { + len = recv(fd, c->buffer + c->buffer_filled_len, BUFFER_SIZE - c->buffer_filled_len, 0); + log_debug("recv(%d, ...)=%ld", fd, len); + if (len < 0) { + if (errno != EWOULDBLOCK && errno != EAGAIN) { + log_error("Error %d in recv from fd=%d: %m", errno, fd); + return -errno; + } + else { + /* recv() is in a blocking state. */ + break; + } + } + else if (len == 0) { + log_debug("Clean disconnection from fd=%d", fd); + total_clients--; + free_connection(c->c_destination); + free_connection(c); + return 0; + } + + assert(len > 0); + log_debug("Recording that the buffer got %ld more bytes full.", len); + c->buffer_filled_len += len; + log_debug("Buffer now has %ld bytes full.", c->buffer_filled_len); + } + + /* Try sending the data immediately. */ + return send_buffer(c); + } + else { + assert(s == c->w_send); + return send_buffer(c->c_destination); + } + + return r; +} + +/* Once sending to the server is unblocked, set up the real watchers. */ +static int connected_to_server_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct sd_event *e = NULL; + struct connection *c_server_to_client = (struct connection *) userdata; + struct connection *c_client_to_server = c_server_to_client->c_destination; + int r; + + assert(revents & EPOLLOUT); + + e = sd_event_get(s); + + /* Cancel the initial write watcher for the server. */ + sd_event_source_unref(s); + + log_debug("Connected to server. Initializing watchers for receiving data."); + + /* A disabled send watcher for the server. */ + r = sd_event_add_io(e, c_server_to_client->fd, EPOLLOUT, transfer_data_cb, c_server_to_client, &c_server_to_client->w_send); + if (r < 0) { + log_error("Error %d creating send watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); + goto fail; + } + r = sd_event_source_set_enabled(c_server_to_client->w_send, SD_EVENT_OFF); + if (r < 0) { + log_error("Error %d muting send for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); + goto finish; + } + + /* A recv watcher for the server. */ + r = sd_event_add_io(e, c_server_to_client->fd, EPOLLIN, transfer_data_cb, c_server_to_client, &c_server_to_client->w_recv); + if (r < 0) { + log_error("Error %d creating recv watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); + goto fail; + } + + /* A disabled send watcher for the client. */ + r = sd_event_add_io(e, c_client_to_server->fd, EPOLLOUT, transfer_data_cb, c_client_to_server, &c_client_to_server->w_send); + if (r < 0) { + log_error("Error %d creating send watcher for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); + goto fail; + } + r = sd_event_source_set_enabled(c_client_to_server->w_send, SD_EVENT_OFF); + if (r < 0) { + log_error("Error %d muting send for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); + goto finish; + } + + /* A recv watcher for the client. */ + r = sd_event_add_io(e, c_client_to_server->fd, EPOLLIN, transfer_data_cb, c_client_to_server, &c_client_to_server->w_recv); + if (r < 0) { + log_error("Error %d creating recv watcher for fd=%d: %s", r, c_client_to_server->fd, strerror(-r)); + goto fail; + } + +goto finish; + +fail: + free_connection(c_client_to_server); + free_connection(c_server_to_client); + +finish: + return r; +} + +static int get_server_connection_fd(const struct proxy *proxy) { + int server_fd; + int r = -EBADF; + int len; + + if (proxy->remote_is_inet) { + int s; + _cleanup_freeaddrinfo_ struct addrinfo *result = NULL; + struct addrinfo hints = {.ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE}; + + log_debug("Looking up address info for %s:%s", proxy->remote_host, proxy->remote_service); + s = getaddrinfo(proxy->remote_host, proxy->remote_service, &hints, &result); + if (s != 0) { + log_error("getaddrinfo error (%d): %s", s, gai_strerror(s)); + return r; + } + + if (result == NULL) { + log_error("getaddrinfo: no result"); + return r; + } + + /* @TODO: Try connecting to all results instead of just the first. */ + server_fd = socket(result->ai_family, result->ai_socktype | SOCK_NONBLOCK, result->ai_protocol); + if (server_fd < 0) { + log_error("Error %d creating socket: %m", errno); + return r; + } + + r = connect(server_fd, result->ai_addr, result->ai_addrlen); + /* Ignore EINPROGRESS errors because they're expected for a nonblocking socket. */ + if (r < 0 && errno != EINPROGRESS) { + log_error("Error %d while connecting to socket %s:%s: %m", errno, proxy->remote_host, proxy->remote_service); + return r; + } + } + else { + struct sockaddr_un remote; + + server_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (server_fd < 0) { + log_error("Error %d creating socket: %m", errno); + return -EBADFD; + } + + remote.sun_family = AF_UNIX; + strncpy(remote.sun_path, proxy->remote_host, sizeof(remote.sun_path)); + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + r = connect(server_fd, (struct sockaddr *) &remote, len); + if (r < 0 && errno != EINPROGRESS) { + log_error("Error %d while connecting to Unix domain socket %s: %m", errno, proxy->remote_host); + return -EBADFD; + } + } + + log_debug("Server connection is fd=%d", server_fd); + return server_fd; +} + +static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + struct proxy *proxy = (struct proxy *) userdata; + struct connection *c_server_to_client; + struct connection *c_client_to_server; + int r = 0; + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + assert(revents & EPOLLIN); + + c_server_to_client = malloc0(sizeof(struct connection)); + if (c_server_to_client == NULL) { + log_oom(); + goto fail; + } + + c_client_to_server = malloc0(sizeof(struct connection)); + if (c_client_to_server == NULL) { + log_oom(); + goto fail; + } + + c_server_to_client->fd = get_server_connection_fd(proxy); + if (c_server_to_client->fd < 0) { + log_error("Error initiating server connection."); + goto fail; + } + + c_client_to_server->fd = accept(fd, (struct sockaddr *) &sa, &salen); + if (c_client_to_server->fd < 0) { + log_error("Error accepting client connection."); + goto fail; + } + + /* Unlike on BSD, client sockets do not inherit nonblocking status + * from the listening socket. */ + r = fd_nonblock(c_client_to_server->fd, true); + if (r < 0) { + log_error("Error %d marking client connection as nonblocking: %s", r, strerror(-r)); + goto fail; + } + + if (sa.sa.sa_family == AF_INET || sa.sa.sa_family == AF_INET6) { + char sa_str[INET6_ADDRSTRLEN]; + const char *success; + + success = inet_ntop(sa.sa.sa_family, &sa.in6.sin6_addr, sa_str, INET6_ADDRSTRLEN); + if (success == NULL) + log_warning("Error %d calling inet_ntop: %m", errno); + else + log_debug("Accepted client connection from %s as fd=%d", sa_str, c_client_to_server->fd); + } + else { + log_debug("Accepted client connection (non-IP) as fd=%d", c_client_to_server->fd); + } + + total_clients++; + log_debug("Client fd=%d (conn %p) successfully connected. Total clients: %u", c_client_to_server->fd, c_client_to_server, total_clients); + log_debug("Server fd=%d (conn %p) successfully initialized.", c_server_to_client->fd, c_server_to_client); + + /* Initialize watcher for send to server; this shows connectivity. */ + r = sd_event_add_io(sd_event_get(s), c_server_to_client->fd, EPOLLOUT, connected_to_server_cb, c_server_to_client, &c_server_to_client->w_send); + if (r < 0) { + log_error("Error %d creating connectivity watcher for fd=%d: %s", r, c_server_to_client->fd, strerror(-r)); + goto fail; + } + + /* Allow lookups of the opposite connection. */ + c_server_to_client->c_destination = c_client_to_server; + c_client_to_server->c_destination = c_server_to_client; + + goto finish; + +fail: + log_warning("Accepting a client connection or connecting to the server failed."); + free_connection(c_client_to_server); + free_connection(c_server_to_client); + +finish: + /* Preserve the main loop even if a single proxy setup fails. */ + return 0; +} + +static int run_main_loop(struct proxy *proxy) { + int r = EXIT_SUCCESS; + struct sd_event *e = NULL; + sd_event_source *w_accept = NULL; + + r = sd_event_new(&e); + if (r < 0) + goto finish; + + r = fd_nonblock(proxy->listen_fd, true); + if (r < 0) + goto finish; + + log_debug("Initializing main listener fd=%d", proxy->listen_fd); + + sd_event_add_io(e, proxy->listen_fd, EPOLLIN, accept_cb, proxy, &w_accept); + + log_debug("Initialized main listener. Entering loop."); + + sd_event_loop(e); + +finish: + sd_event_source_unref(w_accept); + sd_event_unref(e); + + return r; +} + +static int help(void) { + + printf("%s hostname-or-ip port-or-service\n" + "%s unix-domain-socket-path\n\n" + "Inherit a socket. Bidirectionally proxy.\n\n" + " -h --help Show this help\n" + " --version Print version and exit\n" + " --ignore-env Ignore expected systemd environment\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +static void version(void) { + puts(PACKAGE_STRING " saproxy"); +} + +static int parse_argv(int argc, char *argv[], struct proxy *p) { + + enum { + ARG_VERSION = 0x100, + ARG_IGNORE_ENV + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "ignore-env", no_argument, NULL, ARG_IGNORE_ENV}, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case '?': + return -EINVAL; + + case ARG_VERSION: + version(); + return 0; + + case ARG_IGNORE_ENV: + p->ignore_env = true; + continue; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + if (optind + 1 != argc && optind + 2 != argc) { + log_error("Incorrect number of positional arguments."); + help(); + return -EINVAL; + } + + p->remote_host = argv[optind]; + assert(p->remote_host); + + p->remote_is_inet = p->remote_host[0] != '/'; + + if (optind == argc - 2) { + if (!p->remote_is_inet) { + log_error("A port or service is not allowed for Unix socket destinations."); + help(); + return -EINVAL; + } + p->remote_service = argv[optind + 1]; + assert(p->remote_service); + } else if (p->remote_is_inet) { + log_error("A port or service is required for IP destinations."); + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + struct proxy p = {}; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv, &p); + if (r <= 0) + goto finish; + + p.listen_fd = SD_LISTEN_FDS_START; + + if (!p.ignore_env) { + int n; + n = sd_listen_fds(1); + if (n == 0) { + log_error("Found zero inheritable sockets. Are you sure this is running as a socket-activated service?"); + r = EXIT_FAILURE; + goto finish; + } else if (n < 0) { + log_error("Error %d while finding inheritable sockets: %s", n, strerror(-n)); + r = EXIT_FAILURE; + goto finish; + } else if (n > 1) { + log_error("Can't listen on more than one socket."); + r = EXIT_FAILURE; + goto finish; + } + } + + /* @TODO: Check if this proxy can work with datagram sockets. */ + r = sd_is_socket(p.listen_fd, 0, SOCK_STREAM, 1); + if (r < 0) { + log_error("Error %d while checking inherited socket: %s", r, strerror(-r)); + goto finish; + } + + log_info("Starting the socket activation proxy with listener fd=%d.", p.listen_fd); + + r = run_main_loop(&p); + if (r < 0) { + log_error("Error %d from main loop.", r); + goto finish; + } + +finish: + log_close(); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} -- cgit v1.2.3-54-g00ecf