socket-proxyd.c revision da927ba997d68401563b927f92e6e40e021a8e5c
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2013 David Strauss
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 <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "sd-resolve.h"
#include "log.h"
#include "socket-util.h"
#include "util.h"
#include "event-util.h"
#include "build.h"
#include "set.h"
#include "path-util.h"
#define CONNECTIONS_MAX 256
static const char *arg_remote_host = NULL;
typedef struct Context {
} Context;
typedef struct Connection {
} Connection;
static void connection_free(Connection *c) {
assert(c);
if (c->context)
safe_close(c->server_fd);
safe_close(c->client_fd);
free(c);
}
Connection *c;
connection_free(c);
}
int r;
assert(c);
if (buffer[0] >= 0)
return 0;
if (r < 0) {
log_error("Failed to allocate pipe buffer: %m");
return -errno;
}
if (r < 0) {
log_error("Failed to get pipe buffer size: %m");
return -errno;
}
assert(r > 0);
*sz = r;
return 0;
}
static int connection_shovel(
Connection *c,
bool shoveled;
assert(c);
do {
ssize_t z;
shoveled = false;
if (z > 0) {
*full += z;
shoveled = true;
log_error("Failed to splice: %m");
return -errno;
}
}
if (z > 0) {
*full -= z;
shoveled = true;
log_error("Failed to splice: %m");
return -errno;
}
}
} while (shoveled);
return 0;
}
static int connection_enable_event_sources(Connection *c);
Connection *c = userdata;
int r;
assert(s);
assert(c);
r = connection_shovel(c,
&c->server_event_source, &c->client_event_source);
if (r < 0)
goto quit;
r = connection_shovel(c,
&c->client_event_source, &c->server_event_source);
if (r < 0)
goto quit;
/* EOF on both sides? */
goto quit;
/* Server closed, and all data written to client? */
goto quit;
/* Client closed, and all data written to server? */
goto quit;
r = connection_enable_event_sources(c);
if (r < 0)
goto quit;
return 1;
quit:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
static int connection_enable_event_sources(Connection *c) {
uint32_t a = 0, b = 0;
int r;
assert(c);
if (c->server_to_client_buffer_full > 0)
b |= EPOLLOUT;
if (c->server_to_client_buffer_full < c->server_to_client_buffer_size)
a |= EPOLLIN;
if (c->client_to_server_buffer_full > 0)
a |= EPOLLOUT;
if (c->client_to_server_buffer_full < c->client_to_server_buffer_size)
b |= EPOLLIN;
if (c->server_event_source)
r = sd_event_source_set_io_events(c->server_event_source, a);
else if (c->server_fd >= 0)
else
r = 0;
if (r < 0) {
log_error_errno(r, "Failed to set up server event source: %m");
return r;
}
if (c->client_event_source)
r = sd_event_source_set_io_events(c->client_event_source, b);
else if (c->client_fd >= 0)
else
r = 0;
if (r < 0) {
log_error_errno(r, "Failed to set up client event source: %m");
return r;
}
return 0;
}
static int connection_complete(Connection *c) {
int r;
assert(c);
if (r < 0)
goto fail;
if (r < 0)
goto fail;
r = connection_enable_event_sources(c);
if (r < 0)
goto fail;
return 0;
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
Connection *c = userdata;
int error, r;
assert(s);
assert(c);
if (r < 0) {
log_error("Failed to issue SO_ERROR: %m");
goto fail;
}
if (error != 0) {
goto fail;
}
return connection_complete(c);
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
int r;
assert(c);
if (c->client_fd < 0) {
log_error("Failed to get remote socket: %m");
goto fail;
}
if (r < 0) {
if (errno == EINPROGRESS) {
r = sd_event_add_io(c->context->event, &c->client_event_source, c->client_fd, EPOLLOUT, connect_cb, c);
if (r < 0) {
log_error_errno(r, "Failed to add connection socket: %m");
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to enable oneshot event source: %m");
goto fail;
}
} else {
log_error("Failed to connect to remote host: %m");
goto fail;
}
} else {
r = connection_complete(c);
if (r < 0)
goto fail;
}
return 0;
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
Connection *c = userdata;
assert(q);
assert(c);
if (ret != 0) {
goto fail;
}
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
static int resolve_remote(Connection *c) {
};
union sockaddr_union sa = {};
int r;
if (path_is_absolute(arg_remote_host)) {
}
if (arg_remote_host[0] == '@') {
}
if (service) {
service ++;
} else {
service = "80";
}
r = sd_resolve_getaddrinfo(c->context->resolve, &c->resolve_query, node, service, &hints, resolve_cb, c);
if (r < 0) {
log_error_errno(r, "Failed to resolve remote host: %m");
goto fail;
}
return 0;
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
Connection *c;
int r;
log_warning("Hit connection limit, refusing connection.");
safe_close(fd);
return 0;
}
if (r < 0) {
log_oom();
return 0;
}
if (!c) {
log_oom();
return 0;
}
c->client_fd = -1;
if (r < 0) {
free(c);
log_oom();
return 0;
}
return resolve_remote(c);
}
int nfd = -1, r;
assert(s);
if (nfd < 0) {
log_warning("Failed to accept() socket: %m");
} else {
if (r < 0) {
log_error_errno(r, "Failed to accept connection, ignoring: %m");
safe_close(fd);
}
}
if (r < 0) {
log_error_errno(r, "Error while re-enabling listener with ONESHOT: %m");
return r;
}
return 1;
}
int r;
if (r < 0) {
log_oom();
return r;
}
if (r < 0) {
log_error_errno(r, "Failed to determine socket type: %m");
return r;
}
if (r == 0) {
log_error("Passed in socket is not a stream socket.");
return -EINVAL;
}
r = fd_nonblock(fd, true);
if (r < 0) {
log_error_errno(r, "Failed to mark file descriptor non-blocking: %m");
return r;
}
if (r < 0) {
log_error_errno(r, "Failed to add event source: %m");
return r;
}
if (r < 0) {
log_error_errno(r, "Failed to add source to set: %m");
return r;
}
/* Set the watcher to oneshot in case other processes are also
* watching to accept(). */
if (r < 0) {
log_error_errno(r, "Failed to enable oneshot mode: %m");
return r;
}
return 0;
}
static void help(void) {
printf("%1$s [HOST:PORT]\n"
"%1$s [SOCKET]\n\n"
"Bidirectionally proxy local sockets to another (possibly remote) socket.\n\n"
" -h --help Show this help\n"
" --version Show package version\n",
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c;
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
return 0;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
log_error("Not enough parameters.");
return -EINVAL;
}
log_error("Too many parameters.");
return -EINVAL;
}
return 1;
}
int r, n, fd;
log_open();
if (r <= 0)
goto finish;
if (r < 0) {
log_error_errno(r, "Failed to allocate event loop: %m");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to allocate resolver: %m");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to attach resolver: %m");
goto finish;
}
n = sd_listen_fds(1);
if (n < 0) {
log_error("Failed to receive sockets from parent.");
r = n;
goto finish;
} else if (n == 0) {
log_error("Didn't get any sockets passed in.");
r = -EINVAL;
goto finish;
}
if (r < 0)
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to run event loop: %m");
goto finish;
}
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}