/* Copyright StrongLoop, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "defs.h"
#include
#include
#include
/* A connection is modeled as an abstraction on top of two simple state
* machines, one for reading and one for writing. Either state machine
* is, when active, in one of three states: busy, done or stop; the fourth
* and final state, dead, is an end state and only relevant when shutting
* down the connection. A short overview:
*
* busy done stop
* ----------|---------------------------|--------------------|------|
* readable | waiting for incoming data | have incoming data | idle |
* writable | busy writing out data | completed write | idle |
*
* We could remove the done state from the writable state machine. For our
* purposes, it's functionally equivalent to the stop state.
*
* When the connection with upstream has been established, the client_ctx
* moves into a state where incoming data from the client is sent upstream
* and vice versa, incoming data from upstream is sent to the client. In
* other words, we're just piping data back and forth. See conn_cycle()
* for details.
*
* An interesting deviation from libuv's I/O model is that reads are discrete
* rather than continuous events. In layman's terms, when a read operation
* completes, the connection stops reading until further notice.
*
* The rationale for this approach is that we have to wait until the data
* has been sent out again before we can reuse the read buffer.
*
* It also pleasingly unifies with the request model that libuv uses for
* writes and everything else; libuv may switch to a request model for
* reads in the future.
*/
enum conn_state {
c_busy, /* Busy; waiting for incoming data or for a write to complete. */
c_done, /* Done; read incoming data or write finished. */
c_stop, /* Stopped. */
c_dead
};
/* Session states. */
enum sess_state {
s_handshake, /* Wait for client handshake. */
s_handshake_auth, /* Wait for client authentication data. */
s_req_start, /* Start waiting for request data. */
s_req_parse, /* Wait for request data. */
s_req_lookup, /* Wait for upstream hostname DNS lookup to complete. */
s_req_connect, /* Wait for uv_tcp_connect() to complete. */
s_proxy_start, /* Connected. Start piping data. */
s_proxy, /* Connected. Pipe data back and forth. */
s_kill, /* Tear down session. */
s_almost_dead_0, /* Waiting for finalizers to complete. */
s_almost_dead_1, /* Waiting for finalizers to complete. */
s_almost_dead_2, /* Waiting for finalizers to complete. */
s_almost_dead_3, /* Waiting for finalizers to complete. */
s_almost_dead_4, /* Waiting for finalizers to complete. */
s_dead /* Dead. Safe to free now. */
};
static void do_next(client_ctx *cx);
static int do_handshake(client_ctx *cx);
static int do_handshake_auth(client_ctx *cx);
static int do_req_start(client_ctx *cx);
static int do_req_parse(client_ctx *cx);
static int do_req_lookup(client_ctx *cx);
static int do_req_connect_start(client_ctx *cx);
static int do_req_connect(client_ctx *cx);
static int do_proxy_start(client_ctx *cx);
static int do_proxy(client_ctx *cx);
static int do_kill(client_ctx *cx);
static int do_almost_dead(client_ctx *cx);
static int conn_cycle(const char *who, conn *a, conn *b);
static void conn_timer_reset(conn *c);
static void conn_timer_expire(uv_timer_t *handle, int status);
static void conn_getaddrinfo(conn *c, const char *hostname);
static void conn_getaddrinfo_done(uv_getaddrinfo_t *req,
int status,
struct addrinfo *ai);
static int conn_connect(conn *c);
static void conn_connect_done(uv_connect_t *req, int status);
static void conn_read(conn *c);
static void conn_read_done(uv_stream_t *handle,
ssize_t nread,
const uv_buf_t *buf);
static void conn_alloc(uv_handle_t *handle, size_t size, uv_buf_t *buf);
static void conn_write(conn *c, const void *data, unsigned int len);
static void conn_write_done(uv_write_t *req, int status);
static void conn_close(conn *c);
static void conn_close_done(uv_handle_t *handle);
/* |incoming| has been initialized by server.c when this is called. */
void client_finish_init(server_ctx *sx, client_ctx *cx) {
conn *incoming;
conn *outgoing;
cx->sx = sx;
cx->state = s_handshake;
s5_init(&cx->parser);
incoming = &cx->incoming;
incoming->client = cx;
incoming->result = 0;
incoming->rdstate = c_stop;
incoming->wrstate = c_stop;
incoming->idle_timeout = sx->idle_timeout;
CHECK(0 == uv_timer_init(sx->loop, &incoming->timer_handle));
outgoing = &cx->outgoing;
outgoing->client = cx;
outgoing->result = 0;
outgoing->rdstate = c_stop;
outgoing->wrstate = c_stop;
outgoing->idle_timeout = sx->idle_timeout;
CHECK(0 == uv_tcp_init(cx->sx->loop, &outgoing->handle.tcp));
CHECK(0 == uv_timer_init(cx->sx->loop, &outgoing->timer_handle));
/* Wait for the initial packet. */
conn_read(incoming);
}
/* This is the core state machine that drives the client upstream proxy.
* We move through the initial handshake and authentication steps first and
* end up (if all goes well) in the proxy state where we're just proxying
* data between the client and upstream.
*/
static void do_next(client_ctx *cx) {
int new_state;
ASSERT(cx->state != s_dead);
switch (cx->state) {
case s_handshake:
new_state = do_handshake(cx);
break;
case s_handshake_auth:
new_state = do_handshake_auth(cx);
break;
case s_req_start:
new_state = do_req_start(cx);
break;
case s_req_parse:
new_state = do_req_parse(cx);
break;
case s_req_lookup:
new_state = do_req_lookup(cx);
break;
case s_req_connect:
new_state = do_req_connect(cx);
break;
case s_proxy_start:
new_state = do_proxy_start(cx);
break;
case s_proxy:
new_state = do_proxy(cx);
break;
case s_kill:
new_state = do_kill(cx);
break;
case s_almost_dead_0:
case s_almost_dead_1:
case s_almost_dead_2:
case s_almost_dead_3:
case s_almost_dead_4:
new_state = do_almost_dead(cx);
break;
default:
UNREACHABLE();
}
cx->state = new_state;
if (cx->state == s_dead) {
if (DEBUG_CHECKS) {
memset(cx, -1, sizeof(*cx));
}
free(cx);
}
}
static int do_handshake(client_ctx *cx) {
unsigned int methods;
conn *incoming;
s5_ctx *parser;
uint8_t *data;
size_t size;
int err;
parser = &cx->parser;
incoming = &cx->incoming;
ASSERT(incoming->rdstate == c_done);
ASSERT(incoming->wrstate == c_stop);
incoming->rdstate = c_stop;
if (incoming->result < 0) {
pr_err("read error: %s", uv_strerror(incoming->result));
return do_kill(cx);
}
data = (uint8_t *) incoming->t.buf;
size = (size_t) incoming->result;
err = s5_parse(parser, &data, &size);
if (err == s5_ok) {
conn_read(incoming);
return s_handshake; /* Need more data. */
}
if (size != 0) {
/* Could allow a round-trip saving shortcut here if the requested auth
* method is S5_AUTH_NONE (provided unauthenticated traffic is allowed.)
* Requires client support however.
*/
pr_err("junk in handshake");
return do_kill(cx);
}
if (err != s5_auth_select) {
pr_err("handshake error: %s", s5_strerror(err));
return do_kill(cx);
}
methods = s5_auth_methods(parser);
if ((methods & S5_AUTH_NONE) && can_auth_none(cx->sx, cx)) {
s5_select_auth(parser, S5_AUTH_NONE);
conn_write(incoming, "\5\0", 2); /* No auth required. */
return s_req_start;
}
if ((methods & S5_AUTH_PASSWD) && can_auth_passwd(cx->sx, cx)) {
/* TODO(bnoordhuis) Implement username/password auth. */
}
conn_write(incoming, "\5\377", 2); /* No acceptable auth. */
return s_kill;
}
/* TODO(bnoordhuis) Implement username/password auth. */
static int do_handshake_auth(client_ctx *cx) {
UNREACHABLE();
return do_kill(cx);
}
static int do_req_start(client_ctx *cx) {
conn *incoming;
incoming = &cx->incoming;
ASSERT(incoming->rdstate == c_stop);
ASSERT(incoming->wrstate == c_done);
incoming->wrstate = c_stop;
if (incoming->result < 0) {
pr_err("write error: %s", uv_strerror(incoming->result));
return do_kill(cx);
}
conn_read(incoming);
return s_req_parse;
}
static int do_req_parse(client_ctx *cx) {
conn *incoming;
conn *outgoing;
s5_ctx *parser;
uint8_t *data;
size_t size;
int err;
parser = &cx->parser;
incoming = &cx->incoming;
outgoing = &cx->outgoing;
ASSERT(incoming->rdstate == c_done);
ASSERT(incoming->wrstate == c_stop);
ASSERT(outgoing->rdstate == c_stop);
ASSERT(outgoing->wrstate == c_stop);
incoming->rdstate = c_stop;
if (incoming->result < 0) {
pr_err("read error: %s", uv_strerror(incoming->result));
return do_kill(cx);
}
data = (uint8_t *) incoming->t.buf;
size = (size_t) incoming->result;
err = s5_parse(parser, &data, &size);
if (err == s5_ok) {
conn_read(incoming);
return s_req_parse; /* Need more data. */
}
if (size != 0) {
pr_err("junk in request %u", (unsigned) size);
return do_kill(cx);
}
if (err != s5_exec_cmd) {
pr_err("request error: %s", s5_strerror(err));
return do_kill(cx);
}
if (parser->cmd == s5_cmd_tcp_bind) {
/* Not supported but relatively straightforward to implement. */
pr_warn("BIND requests are not supported.");
return do_kill(cx);
}
if (parser->cmd == s5_cmd_udp_assoc) {
/* Not supported. Might be hard to implement because libuv has no
* functionality for detecting the MTU size which the RFC mandates.
*/
pr_warn("UDP ASSOC requests are not supported.");
return do_kill(cx);
}
ASSERT(parser->cmd == s5_cmd_tcp_connect);
if (parser->atyp == s5_atyp_host) {
conn_getaddrinfo(outgoing, (const char *) parser->daddr);
return s_req_lookup;
}
if (parser->atyp == s5_atyp_ipv4) {
memset(&outgoing->t.addr4, 0, sizeof(outgoing->t.addr4));
outgoing->t.addr4.sin_family = AF_INET;
outgoing->t.addr4.sin_port = htons(parser->dport);
memcpy(&outgoing->t.addr4.sin_addr,
parser->daddr,
sizeof(outgoing->t.addr4.sin_addr));
} else if (parser->atyp == s5_atyp_ipv6) {
memset(&outgoing->t.addr6, 0, sizeof(outgoing->t.addr6));
outgoing->t.addr6.sin6_family = AF_INET6;
outgoing->t.addr6.sin6_port = htons(parser->dport);
memcpy(&outgoing->t.addr6.sin6_addr,
parser->daddr,
sizeof(outgoing->t.addr6.sin6_addr));
} else {
UNREACHABLE();
}
return do_req_connect_start(cx);
}
static int do_req_lookup(client_ctx *cx) {
s5_ctx *parser;
conn *incoming;
conn *outgoing;
parser = &cx->parser;
incoming = &cx->incoming;
outgoing = &cx->outgoing;
ASSERT(incoming->rdstate == c_stop);
ASSERT(incoming->wrstate == c_stop);
ASSERT(outgoing->rdstate == c_stop);
ASSERT(outgoing->wrstate == c_stop);
if (outgoing->result < 0) {
/* TODO(bnoordhuis) Escape control characters in parser->daddr. */
pr_err("lookup error for \"%s\": %s",
parser->daddr,
uv_strerror(outgoing->result));
/* Send back a 'Host unreachable' reply. */
conn_write(incoming, "\5\4\0\1\0\0\0\0\0\0", 10);
return s_kill;
}
/* Don't make assumptions about the offset of sin_port/sin6_port. */
switch (outgoing->t.addr.sa_family) {
case AF_INET:
outgoing->t.addr4.sin_port = htons(parser->dport);
break;
case AF_INET6:
outgoing->t.addr6.sin6_port = htons(parser->dport);
break;
default:
UNREACHABLE();
}
return do_req_connect_start(cx);
}
/* Assumes that cx->outgoing.t.sa contains a valid AF_INET/AF_INET6 address. */
static int do_req_connect_start(client_ctx *cx) {
conn *incoming;
conn *outgoing;
int err;
incoming = &cx->incoming;
outgoing = &cx->outgoing;
ASSERT(incoming->rdstate == c_stop);
ASSERT(incoming->wrstate == c_stop);
ASSERT(outgoing->rdstate == c_stop);
ASSERT(outgoing->wrstate == c_stop);
if (!can_access(cx->sx, cx, &outgoing->t.addr)) {
pr_warn("connection not allowed by ruleset");
/* Send a 'Connection not allowed by ruleset' reply. */
conn_write(incoming, "\5\2\0\1\0\0\0\0\0\0", 10);
return s_kill;
}
err = conn_connect(outgoing);
if (err != 0) {
pr_err("connect error: %s\n", uv_strerror(err));
return do_kill(cx);
}
return s_req_connect;
}
static int do_req_connect(client_ctx *cx) {
const struct sockaddr_in6 *in6;
const struct sockaddr_in *in;
char addr_storage[sizeof(*in6)];
conn *incoming;
conn *outgoing;
uint8_t *buf;
int addrlen;
incoming = &cx->incoming;
outgoing = &cx->outgoing;
ASSERT(incoming->rdstate == c_stop);
ASSERT(incoming->wrstate == c_stop);
ASSERT(outgoing->rdstate == c_stop);
ASSERT(outgoing->wrstate == c_stop);
/* Build and send the reply. Not very pretty but gets the job done. */
buf = (uint8_t *) incoming->t.buf;
if (outgoing->result == 0) {
/* The RFC mandates that the SOCKS server must include the local port
* and address in the reply. So that's what we do.
*/
addrlen = sizeof(addr_storage);
CHECK(0 == uv_tcp_getsockname(&outgoing->handle.tcp,
(struct sockaddr *) addr_storage,
&addrlen));
buf[0] = 5; /* Version. */
buf[1] = 0; /* Success. */
buf[2] = 0; /* Reserved. */
if (addrlen == sizeof(*in)) {
buf[3] = 1; /* IPv4. */
in = (const struct sockaddr_in *) &addr_storage;
memcpy(buf + 4, &in->sin_addr, 4);
memcpy(buf + 8, &in->sin_port, 2);
conn_write(incoming, buf, 10);
} else if (addrlen == sizeof(*in6)) {
buf[3] = 4; /* IPv6. */
in6 = (const struct sockaddr_in6 *) &addr_storage;
memcpy(buf + 4, &in6->sin6_addr, 16);
memcpy(buf + 20, &in6->sin6_port, 2);
conn_write(incoming, buf, 22);
} else {
UNREACHABLE();
}
return s_proxy_start;
} else {
pr_err("upstream connection error: %s\n", uv_strerror(outgoing->result));
/* Send a 'Connection refused' reply. */
conn_write(incoming, "\5\5\0\1\0\0\0\0\0\0", 10);
return s_kill;
}
UNREACHABLE();
return s_kill;
}
static int do_proxy_start(client_ctx *cx) {
conn *incoming;
conn *outgoing;
incoming = &cx->incoming;
outgoing = &cx->outgoing;
ASSERT(incoming->rdstate == c_stop);
ASSERT(incoming->wrstate == c_done);
ASSERT(outgoing->rdstate == c_stop);
ASSERT(outgoing->wrstate == c_stop);
incoming->wrstate = c_stop;
if (incoming->result < 0) {
pr_err("write error: %s", uv_strerror(incoming->result));
return do_kill(cx);
}
conn_read(incoming);
conn_read(outgoing);
return s_proxy;
}
/* Proxy incoming data back and forth. */
static int do_proxy(client_ctx *cx) {
if (conn_cycle("client", &cx->incoming, &cx->outgoing)) {
return do_kill(cx);
}
if (conn_cycle("upstream", &cx->outgoing, &cx->incoming)) {
return do_kill(cx);
}
return s_proxy;
}
static int do_kill(client_ctx *cx) {
int new_state;
if (cx->state >= s_almost_dead_0) {
return cx->state;
}
/* Try to cancel the request. The callback still runs but if the
* cancellation succeeded, it gets called with status=UV_ECANCELED.
*/
new_state = s_almost_dead_1;
if (cx->state == s_req_lookup) {
new_state = s_almost_dead_0;
uv_cancel(&cx->outgoing.t.req);
}
conn_close(&cx->incoming);
conn_close(&cx->outgoing);
return new_state;
}
static int do_almost_dead(client_ctx *cx) {
ASSERT(cx->state >= s_almost_dead_0);
return cx->state + 1; /* Another finalizer completed. */
}
static int conn_cycle(const char *who, conn *a, conn *b) {
if (a->result < 0) {
if (a->result != UV_EOF) {
pr_err("%s error: %s", who, uv_strerror(a->result));
}
return -1;
}
if (b->result < 0) {
return -1;
}
if (a->wrstate == c_done) {
a->wrstate = c_stop;
}
/* The logic is as follows: read when we don't write and write when we don't
* read. That gives us back-pressure handling for free because if the peer
* sends data faster than we consume it, TCP congestion control kicks in.
*/
if (a->wrstate == c_stop) {
if (b->rdstate == c_stop) {
conn_read(b);
} else if (b->rdstate == c_done) {
conn_write(a, b->t.buf, b->result);
b->rdstate = c_stop; /* Triggers the call to conn_read() above. */
}
}
return 0;
}
static void conn_timer_reset(conn *c) {
CHECK(0 == uv_timer_start(&c->timer_handle,
conn_timer_expire,
c->idle_timeout,
0));
}
static void conn_timer_expire(uv_timer_t *handle, int status) {
conn *c;
CHECK(0 == status);
c = CONTAINER_OF(handle, conn, timer_handle);
c->result = UV_ETIMEDOUT;
do_next(c->client);
}
static void conn_getaddrinfo(conn *c, const char *hostname) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
CHECK(0 == uv_getaddrinfo(c->client->sx->loop,
&c->t.addrinfo_req,
conn_getaddrinfo_done,
hostname,
NULL,
&hints));
conn_timer_reset(c);
}
static void conn_getaddrinfo_done(uv_getaddrinfo_t *req,
int status,
struct addrinfo *ai) {
conn *c;
c = CONTAINER_OF(req, conn, t.addrinfo_req);
c->result = status;
if (status == 0) {
/* FIXME(bnoordhuis) Should try all addresses. */
if (ai->ai_family == AF_INET) {
c->t.addr4 = *(const struct sockaddr_in *) ai->ai_addr;
} else if (ai->ai_family == AF_INET6) {
c->t.addr6 = *(const struct sockaddr_in6 *) ai->ai_addr;
} else {
UNREACHABLE();
}
}
uv_freeaddrinfo(ai);
do_next(c->client);
}
/* Assumes that c->t.sa contains a valid AF_INET or AF_INET6 address. */
static int conn_connect(conn *c) {
ASSERT(c->t.addr.sa_family == AF_INET ||
c->t.addr.sa_family == AF_INET6);
conn_timer_reset(c);
return uv_tcp_connect(&c->t.connect_req,
&c->handle.tcp,
&c->t.addr,
conn_connect_done);
}
static void conn_connect_done(uv_connect_t *req, int status) {
conn *c;
if (status == UV_ECANCELED) {
return; /* Handle has been closed. */
}
c = CONTAINER_OF(req, conn, t.connect_req);
c->result = status;
do_next(c->client);
}
static void conn_read(conn *c) {
ASSERT(c->rdstate == c_stop);
CHECK(0 == uv_read_start(&c->handle.stream, conn_alloc, conn_read_done));
c->rdstate = c_busy;
conn_timer_reset(c);
}
static void conn_read_done(uv_stream_t *handle,
ssize_t nread,
const uv_buf_t *buf) {
conn *c;
c = CONTAINER_OF(handle, conn, handle);
ASSERT(c->t.buf == buf->base);
ASSERT(c->rdstate == c_busy);
c->rdstate = c_done;
c->result = nread;
uv_read_stop(&c->handle.stream);
do_next(c->client);
}
static void conn_alloc(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
conn *c;
c = CONTAINER_OF(handle, conn, handle);
ASSERT(c->rdstate == c_busy);
buf->base = c->t.buf;
buf->len = sizeof(c->t.buf);
}
static void conn_write(conn *c, const void *data, unsigned int len) {
uv_buf_t buf;
ASSERT(c->wrstate == c_stop || c->wrstate == c_done);
c->wrstate = c_busy;
/* It's okay to cast away constness here, uv_write() won't modify the
* memory.
*/
buf.base = (char *) data;
buf.len = len;
CHECK(0 == uv_write(&c->write_req,
&c->handle.stream,
&buf,
1,
conn_write_done));
conn_timer_reset(c);
}
static void conn_write_done(uv_write_t *req, int status) {
conn *c;
if (status == UV_ECANCELED) {
return; /* Handle has been closed. */
}
c = CONTAINER_OF(req, conn, write_req);
ASSERT(c->wrstate == c_busy);
c->wrstate = c_done;
c->result = status;
do_next(c->client);
}
static void conn_close(conn *c) {
ASSERT(c->rdstate != c_dead);
ASSERT(c->wrstate != c_dead);
c->rdstate = c_dead;
c->wrstate = c_dead;
c->timer_handle.data = c;
c->handle.handle.data = c;
uv_close(&c->handle.handle, conn_close_done);
uv_close((uv_handle_t *) &c->timer_handle, conn_close_done);
}
static void conn_close_done(uv_handle_t *handle) {
conn *c;
c = handle->data;
do_next(c->client);
}
/* Copyright StrongLoop, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "defs.h"
#include /* INET6_ADDRSTRLEN */
#include
#include
#ifndef INET6_ADDRSTRLEN
# define INET6_ADDRSTRLEN 63
#endif
typedef struct {
uv_getaddrinfo_t getaddrinfo_req;
server_config config;
server_ctx *servers;
uv_loop_t *loop;
} server_state;
static void do_bind(uv_getaddrinfo_t *req, int status, struct addrinfo *ai);
static void on_connection(uv_stream_t *server, int status);
int server_run(const server_config *cf, uv_loop_t *loop) {
struct addrinfo hints;
server_state state;
int err;
memset(&state, 0, sizeof(state));
state.servers = NULL;
state.config = *cf;
state.loop = loop;
/* Resolve the address of the interface that we should bind to.
* The getaddrinfo callback starts the server and everything else.
*/
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = uv_getaddrinfo(loop,
&state.getaddrinfo_req,
do_bind,
cf->bind_host,
NULL,
&hints);
if (err != 0) {
pr_err("getaddrinfo: %s", uv_strerror(err));
return err;
}
/* Start the event loop. Control continues in do_bind(). */
if (uv_run(loop, UV_RUN_DEFAULT)) {
abort();
}
/* Please Valgrind. */
uv_loop_delete(loop);
free(state.servers);
return 0;
}
/* Bind a server to each address that getaddrinfo() reported. */
static void do_bind(uv_getaddrinfo_t *req, int status, struct addrinfo *addrs) {
char addrbuf[INET6_ADDRSTRLEN + 1];
unsigned int ipv4_naddrs;
unsigned int ipv6_naddrs;
server_state *state;
server_config *cf;
struct addrinfo *ai;
const void *addrv;
const char *what;
uv_loop_t *loop;
server_ctx *sx;
unsigned int n;
int err;
union {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
} s;
state = CONTAINER_OF(req, server_state, getaddrinfo_req);
loop = state->loop;
cf = &state->config;
if (status < 0) {
pr_err("getaddrinfo(\"%s\"): %s", cf->bind_host, uv_strerror(status));
uv_freeaddrinfo(addrs);
return;
}
ipv4_naddrs = 0;
ipv6_naddrs = 0;
for (ai = addrs; ai != NULL; ai = ai->ai_next) {
if (ai->ai_family == AF_INET) {
ipv4_naddrs += 1;
} else if (ai->ai_family == AF_INET6) {
ipv6_naddrs += 1;
}
}
if (ipv4_naddrs == 0 && ipv6_naddrs == 0) {
pr_err("%s has no IPv4/6 addresses", cf->bind_host);
uv_freeaddrinfo(addrs);
return;
}
state->servers =
xmalloc((ipv4_naddrs + ipv6_naddrs) * sizeof(state->servers[0]));
n = 0;
for (ai = addrs; ai != NULL; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
continue;
}
if (ai->ai_family == AF_INET) {
s.addr4 = *(const struct sockaddr_in *) ai->ai_addr;
s.addr4.sin_port = htons(cf->bind_port);
addrv = &s.addr4.sin_addr;
} else if (ai->ai_family == AF_INET6) {
s.addr6 = *(const struct sockaddr_in6 *) ai->ai_addr;
s.addr6.sin6_port = htons(cf->bind_port);
addrv = &s.addr6.sin6_addr;
} else {
UNREACHABLE();
}
if (uv_inet_ntop(s.addr.sa_family, addrv, addrbuf, sizeof(addrbuf))) {
UNREACHABLE();
}
sx = state->servers + n;
sx->loop = loop;
sx->idle_timeout = state->config.idle_timeout;
CHECK(0 == uv_tcp_init(loop, &sx->tcp_handle));
what = "uv_tcp_bind";
err = uv_tcp_bind(&sx->tcp_handle, &s.addr, 0);
if (err == 0) {
what = "uv_listen";
err = uv_listen((uv_stream_t *) &sx->tcp_handle, 128, on_connection);
}
if (err != 0) {
pr_err("%s(\"%s:%hu\"): %s",
what,
addrbuf,
cf->bind_port,
uv_strerror(err));
while (n > 0) {
n -= 1;
uv_close((uv_handle_t *) (state->servers + n), NULL);
}
break;
}
pr_info("listening on %s:%hu", addrbuf, cf->bind_port);
n += 1;
}
uv_freeaddrinfo(addrs);
}
static void on_connection(uv_stream_t *server, int status) {
server_ctx *sx;
client_ctx *cx;
CHECK(status == 0);
sx = CONTAINER_OF(server, server_ctx, tcp_handle);
cx = xmalloc(sizeof(*cx));
CHECK(0 == uv_tcp_init(sx->loop, &cx->incoming.handle.tcp));
CHECK(0 == uv_accept(server, &cx->incoming.handle.stream));
client_finish_init(sx, cx);
}
int can_auth_none(const server_ctx *sx, const client_ctx *cx) {
return 1;
}
int can_auth_passwd(const server_ctx *sx, const client_ctx *cx) {
return 0;
}
int can_access(const server_ctx *sx,
const client_ctx *cx,
const struct sockaddr *addr) {
const struct sockaddr_in6 *addr6;
const struct sockaddr_in *addr4;
const uint32_t *p;
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
/* TODO(bnoordhuis) Implement proper access checks. For now, just reject
* traffic to localhost.
*/
if (addr->sa_family == AF_INET) {
addr4 = (const struct sockaddr_in *) addr;
d = ntohl(addr4->sin_addr.s_addr);
return (d >> 24) != 0x7F;
}
if (addr->sa_family == AF_INET6) {
addr6 = (const struct sockaddr_in6 *) addr;
p = (const uint32_t *) &addr6->sin6_addr.s6_addr;
a = ntohl(p[0]);
b = ntohl(p[1]);
c = ntohl(p[2]);
d = ntohl(p[3]);
if (a == 0 && b == 0 && c == 0 && d == 1) {
return 0; /* "::1" style address. */
}
if (a == 0 && b == 0 && c == 0xFFFF && (d >> 24) == 0x7F) {
return 0; /* "::ffff:127.x.x.x" style address. */
}
return 1;
}
return 0;
}
/* Copyright StrongLoop, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "defs.h"
#include
#include
#include
#if HAVE_UNISTD_H
#include /* getopt */
#endif
#define DEFAULT_BIND_HOST "127.0.0.1"
#define DEFAULT_BIND_PORT 1080
#define DEFAULT_IDLE_TIMEOUT (60 * 1000)
static void parse_opts(server_config *cf, int argc, char **argv);
static void usage(void);
static const char *progname = __FILE__; /* Reset in main(). */
int main(int argc, char **argv) {
server_config config;
int err;
progname = argv[0];
memset(&config, 0, sizeof(config));
config.bind_host = DEFAULT_BIND_HOST;
config.bind_port = DEFAULT_BIND_PORT;
config.idle_timeout = DEFAULT_IDLE_TIMEOUT;
parse_opts(&config, argc, argv);
err = server_run(&config, uv_default_loop());
if (err) {
exit(1);
}
return 0;
}
const char *_getprogname(void) {
return progname;
}
static void parse_opts(server_config *cf, int argc, char **argv) {
int opt;
while (-1 != (opt = getopt(argc, argv, "H:hp:"))) {
switch (opt) {
case 'H':
cf->bind_host = optarg;
break;
case 'p':
if (1 != sscanf(optarg, "%hu", &cf->bind_port)) {
pr_err("bad port number: %s", optarg);
usage();
}
break;
default:
usage();
}
}
}
static void usage(void) {
printf("Usage:\n"
"\n"
" %s [-b [-h] [-p ]\n"
"\n"
"Options:\n"
"\n"
" -b Bind to this address or hostname.\n"
" Default: \"127.0.0.1\"\n"
" -h Show this help message.\n"
" -p Bind to this port number. Default: 1080\n"
"",
progname);
exit(1);
}