socket-proxyd.c revision b7484e2a58038c57591457c1439505607bdcd833
/*-*- 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 <unistd.h>
#include "sd-daemon.h"
#include "sd-event.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
typedef struct Context {
} Context;
typedef struct Connection {
} Connection;
union sockaddr_any {
struct sockaddr_un un;
struct sockaddr_in in;
struct sockaddr_in6 in6;
struct sockaddr_storage storage;
};
static const char *arg_remote_host = NULL;
static void connection_free(Connection *c) {
assert(c);
if (c->server_fd >= 0)
if (c->client_fd >= 0)
free(c);
}
Connection *c;
connection_free(c);
}
int r;
if (path_is_absolute(arg_remote_host)) {
} else if (arg_remote_host[0] == '@') {
} else {
};
if (service) {
service ++;
} else {
service = "80";
}
if (r != 0) {
return -EHOSTUNREACH;
}
log_error("Address too long.");
return -E2BIG;
}
}
return 0;
}
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;
*from = -1;
log_error("Failed to splice: %m");
return -errno;
}
}
if (z > 0) {
*full -= z;
shoveled = true;
*to = -1;
log_error("Failed to splice: %m");
return -errno;
}
}
} while (shoveled);
return 0;
}
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, sd_event_get(s));
if (r < 0)
goto quit;
return 1;
quit:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
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) {
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) {
return r;
}
return 0;
}
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;
}
if (r < 0)
goto fail;
if (r < 0)
goto fail;
r = connection_enable_event_sources(c, sd_event_get(s));
if (r < 0)
goto fail;
return 0;
fail:
connection_free(c);
return 0; /* ignore errors, continue serving */
}
union sockaddr_any sa = {};
Connection *c;
int r;
log_warning("Hit connection limit, refusing connection.");
return 0;
}
if (r < 0)
return log_oom();
if (!c)
return log_oom();
c->client_fd = -1;
if (r < 0)
goto fail;
if (c->client_fd < 0) {
log_error("Failed to get remote socket: %m");
goto fail;
}
if (r < 0) {
if (errno == EINPROGRESS) {
if (r < 0) {
goto fail;
}
if (r < 0) {
goto fail;
}
} else {
log_error("Failed to connect to remote host: %m");
goto fail;
}
} else {
r = connection_enable_event_sources(c, event);
if (r < 0)
goto fail;
}
return 0;
fail:
connection_free(c);
return 0; /* ignore non-OOM errors, continue serving */
}
int nfd = -1, r;
assert(s);
if (nfd >= 0) {
if (r < 0) {
return r;
}
log_warning("Failed to accept() socket: %m");
if (r < 0) {
return r;
}
return 1;
}
int r;
if (r < 0) {
log_oom();
return r;
}
if (r < 0) {
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) {
return r;
}
if (r < 0) {
return r;
}
if (r < 0) {
return r;
}
/* Set the watcher to oneshot in case other processes are also
* watching to accept(). */
if (r < 0) {
return r;
}
return 0;
}
static int help(void) {
printf("%s [HOST:PORT]\n"
"%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",
return 0;
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c;
switch (c) {
case 'h':
return help();
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;
r = sd_event_new(&event);
if (r < 0) {
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;
}
r = sd_event_loop(event);
if (r < 0) {
goto finish;
}
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}