/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2018 Leonid Evdokimov * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "parser.h" #include "log.h" #include "main.h" #include "base.h" #include "redsocks.h" #include "utils.h" #include "libevent-compat.h" #define REDSOCKS_RELAY_HALFBUFF 4096 enum pump_state_t { pump_active = -1, pump_MAX = 0, }; static const char *redsocks_event_str(unsigned short what); static int redsocks_start_bufferpump(redsocks_client *client); static int redsocks_start_splicepump(redsocks_client *client); static void redsocks_conn_list_del(redsocks_client *client); extern relay_subsys http_connect_subsys; extern relay_subsys http_relay_subsys; extern relay_subsys socks4_subsys; extern relay_subsys socks5_subsys; static relay_subsys *relay_subsystems[] = { &http_connect_subsys, &http_relay_subsys, &socks4_subsys, &socks5_subsys, }; static list_head instances = LIST_HEAD_INIT(instances); // Managing connection pressure. static uint32_t redsocks_conn; static uint32_t accept_backoff_ms; static struct event accept_backoff_ev; static parser_entry redsocks_entries[] = { { .key = "local_ip", .type = pt_in_addr }, { .key = "local_port", .type = pt_uint16 }, { .key = "ip", .type = pt_in_addr }, { .key = "port", .type = pt_uint16 }, { .key = "type", .type = pt_pchar }, { .key = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "listenq", .type = pt_uint16 }, { .key = "splice", .type = pt_bool }, { .key = "disclose_src", .type = pt_disclose_src }, { .key = "on_proxy_fail", .type = pt_on_proxy_fail }, { } }; static bool is_splice_good() { struct utsname u; if (uname(&u) != 0) { return false; } unsigned long int v[4] = { 0, 0, 0, 0 }; char *rel = u.release; for (int i = 0; i < SIZEOF_ARRAY(v); ++i) { v[i] = strtoul(rel, &rel, 0); while (*rel && !isdigit(*rel)) ++rel; } // haproxy assumes that splice "works" for 2.6.27.13+ return (v[0] > 2) || (v[0] == 2 && v[1] > 6) || (v[0] == 2 && v[1] == 6 && v[2] > 27) || (v[0] == 2 && v[1] == 6 && v[2] == 27 && v[3] >= 13); } static int redsocks_onenter(parser_section *section) { // FIXME: find proper way to calulate instance_payload_len int instance_payload_len = 0; relay_subsys **ss; FOREACH(ss, relay_subsystems) if (instance_payload_len < (*ss)->instance_payload_len) instance_payload_len = (*ss)->instance_payload_len; redsocks_instance *instance = calloc(1, sizeof(*instance) + instance_payload_len); if (!instance) { parser_error(section->context, "Not enough memory"); return -1; } INIT_LIST_HEAD(&instance->list); INIT_LIST_HEAD(&instance->clients); instance->config.bindaddr.sin_family = AF_INET; instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); instance->config.relayaddr.sin_family = AF_INET; instance->config.relayaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* Default value can be checked in run-time, but I doubt anyone needs that. * Linux: sysctl net.core.somaxconn * FreeBSD: sysctl kern.ipc.somaxconn */ instance->config.listenq = SOMAXCONN; instance->config.use_splice = is_splice_good(); instance->config.disclose_src = DISCLOSE_NONE; instance->config.on_proxy_fail = ONFAIL_CLOSE; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = (strcmp(entry->key, "local_ip") == 0) ? (void*)&instance->config.bindaddr.sin_addr : (strcmp(entry->key, "local_port") == 0) ? (void*)&instance->config.bindaddr.sin_port : (strcmp(entry->key, "ip") == 0) ? (void*)&instance->config.relayaddr.sin_addr : (strcmp(entry->key, "port") == 0) ? (void*)&instance->config.relayaddr.sin_port : (strcmp(entry->key, "type") == 0) ? (void*)&instance->config.type : (strcmp(entry->key, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "listenq") == 0) ? (void*)&instance->config.listenq : (strcmp(entry->key, "splice") == 0) ? (void*)&instance->config.use_splice : (strcmp(entry->key, "disclose_src") == 0) ? (void*)&instance->config.disclose_src : (strcmp(entry->key, "on_proxy_fail") == 0) ? (void*)&instance->config.on_proxy_fail : NULL; section->data = instance; return 0; } static int redsocks_onexit(parser_section *section) { /* FIXME: Rewrite in bullet-proof style. There are memory leaks if config * file is not correct, so correct on-the-fly config reloading is * currently impossible. */ redsocks_instance *instance = section->data; section->data = NULL; for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = NULL; instance->config.bindaddr.sin_port = htons(instance->config.bindaddr.sin_port); instance->config.relayaddr.sin_port = htons(instance->config.relayaddr.sin_port); if (instance->config.type) { relay_subsys **ss; FOREACH(ss, relay_subsystems) { if (!strcmp((*ss)->name, instance->config.type)) { instance->relay_ss = *ss; list_add(&instance->list, &instances); break; } } if (!instance->relay_ss) { parser_error(section->context, "invalid `type` <%s> for redsocks", instance->config.type); return -1; } } else { parser_error(section->context, "no `type` for redsocks"); return -1; } if (instance->config.disclose_src != DISCLOSE_NONE && instance->relay_ss != &http_connect_subsys) { parser_error(section->context, "only `http-connect` supports `disclose_src` at the moment"); return -1; } if (instance->config.on_proxy_fail != ONFAIL_CLOSE && instance->relay_ss != &http_connect_subsys) { parser_error(section->context, "only `http-connect` supports `on_proxy_fail` at the moment"); return -1; } return 0; } static parser_section redsocks_conf_section = { .name = "redsocks", .entries = redsocks_entries, .onenter = redsocks_onenter, .onexit = redsocks_onexit }; void redsocks_log_write_plain( const char *file, int line, const char *func, int do_errno, const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, int priority, const char *orig_fmt, ... ) { if (!should_log(priority)) return; int saved_errno = errno; struct evbuffer *fmt = evbuffer_new(); va_list ap; char clientaddr_str[RED_INET_ADDRSTRLEN], destaddr_str[RED_INET_ADDRSTRLEN]; if (!fmt) { log_errno(LOG_ERR, "evbuffer_new()"); // no return, as I have to call va_start/va_end } if (fmt) { evbuffer_add_printf(fmt, "[%s->%s]: %s", red_inet_ntop(clientaddr, clientaddr_str, sizeof(clientaddr_str)), red_inet_ntop(destaddr, destaddr_str, sizeof(destaddr_str)), orig_fmt); } va_start(ap, orig_fmt); if (fmt) { errno = saved_errno; _log_vwrite(file, line, func, do_errno, priority, (const char*)evbuffer_pullup(fmt, -1), ap); evbuffer_free(fmt); } va_end(ap); } void redsocks_touch_client(redsocks_client *client) { redsocks_gettimeofday(&client->last_event); } static bool shut_both(redsocks_client *client) { return client->relay_evshut == (EV_READ|EV_WRITE) && client->client_evshut == (EV_READ|EV_WRITE); } static int bufprio(redsocks_client *client, struct bufferevent *buffev) { // client errors are logged with LOG_INFO, server errors with LOG_NOTICE return (buffev == client->client) ? LOG_INFO : LOG_NOTICE; } static const char* bufname(redsocks_client *client, struct bufferevent *buf) { assert(buf == client->client || buf == client->relay); return buf == client->client ? "client" : "relay"; } static void redsocks_relay_readcb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { if (evbuffer_get_length(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); } else { if (bufferevent_get_enabled(from) & EV_READ) { redsocks_log_error(client, LOG_DEBUG, "backpressure: bufferevent_disable(%s, EV_READ)", bufname(client, from)); if (bufferevent_disable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); } } } static void redsocks_relay_writecb(redsocks_client *client, struct bufferevent *from, struct bufferevent *to) { assert(from == client->client || from == client->relay); char from_eof = (from == client->client ? client->client_evshut : client->relay_evshut) & EV_READ; if (evbuffer_get_length(from->input) == 0 && from_eof) { redsocks_shutdown(client, to, SHUT_WR); } else if (evbuffer_get_length(to->output) < to->wm_write.high) { if (bufferevent_write_buffer(to, from->input) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); if (!from_eof && !(bufferevent_get_enabled(from) & EV_READ)) { redsocks_log_error(client, LOG_DEBUG, "backpressure: bufferevent_enable(%s, EV_READ)", bufname(client, from)); if (bufferevent_enable(from, EV_READ) == -1) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); } } } static void redsocks_relay_relayreadcb(struct bufferevent *from, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_readcb(client, client->relay, client->client); } static void redsocks_relay_relaywritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_writecb(client, client->client, client->relay); } static void redsocks_relay_clientreadcb(struct bufferevent *from, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_readcb(client, client->client, client->relay); } static void redsocks_relay_clientwritecb(struct bufferevent *to, void *_client) { redsocks_client *client = _client; redsocks_touch_client(client); redsocks_relay_writecb(client, client->relay, client->client); } void redsocks_start_relay(redsocks_client *client) { if (client->instance->relay_ss->fini) client->instance->relay_ss->fini(client); client->state = pump_active; int error = ((client->instance->config.use_splice) ? redsocks_start_splicepump : redsocks_start_bufferpump)(client); if (!error) redsocks_log_error(client, LOG_DEBUG, "data relaying started"); else redsocks_drop_client(client); } static int redsocks_start_bufferpump(redsocks_client *client) { // wm_write.high is respected by libevent-2.0.22 only for ssl and filters, // so it's implemented in redsocks callbacks. wm_read.high works as expected. bufferevent_setwatermark(client->client, EV_READ|EV_WRITE, 0, REDSOCKS_RELAY_HALFBUFF); bufferevent_setwatermark(client->relay, EV_READ|EV_WRITE, 0, REDSOCKS_RELAY_HALFBUFF); client->client->readcb = redsocks_relay_clientreadcb; client->client->writecb = redsocks_relay_clientwritecb; client->relay->readcb = redsocks_relay_relayreadcb; client->relay->writecb = redsocks_relay_relaywritecb; int error = bufferevent_enable(client->client, (EV_READ|EV_WRITE) & ~(client->client_evshut)); if (!error) error = bufferevent_enable(client->relay, (EV_READ|EV_WRITE) & ~(client->relay_evshut)); if (error) redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); return error; } static int pipeprio(redsocks_pump *pump, int fd) { // client errors are logged with LOG_INFO, server errors with LOG_NOTICE return (fd == event_get_fd(&pump->client_read)) ? LOG_INFO : LOG_NOTICE; } static const char* pipename(redsocks_pump *pump, int fd) { return (fd == event_get_fd(&pump->client_read)) ? "client" : "relay"; } static void bufferevent_free_unused(struct bufferevent **p) { if (*p && !evbuffer_get_length((*p)->input) && !evbuffer_get_length((*p)->output)) { bufferevent_free(*p); *p = NULL; } } static bool would_block(int e) { return e == EAGAIN || e == EWOULDBLOCK; } typedef struct redsplice_write_ctx_t { // drain ebsrc[0], ebsrc[1], pisrc in that order struct evbuffer *ebsrc[2]; splice_pipe *pisrc; struct event *evsrc; struct event *evdst; const evshut_t *shut_src; evshut_t *shut_dst; } redsplice_write_ctx; static void redsplice_write_cb(redsocks_pump *pump, redsplice_write_ctx *c, int out) { bool has_data = false; // there is some pending data to be written bool can_write = true; // socket SEEMS TO BE writable // short write -- goto read/write management // write error -- drop client alltogether // full write -- take next buffer // got EOF & no data -- relay EOF for (int i = 0; i < SIZEOF_ARRAY(c->ebsrc); ++i) { struct evbuffer *ebsrc = c->ebsrc[i]; if (ebsrc) { const size_t avail = evbuffer_get_length(ebsrc); has_data = !!avail; if (avail) { const ssize_t sent = evbuffer_write(ebsrc, out); if (sent == -1) { if (would_block(errno)) { // short (zero) write can_write = false; goto decide; } else { redsocks_log_errno(&pump->c, pipeprio(pump, out), "evbuffer_write(to %s, %zu)", pipename(pump, out), avail); redsocks_drop_client(&pump->c); return; } } else if (avail == sent) { has_data = false; // unless stated otherwise } else { // short write can_write = false; goto decide; } } } } do { has_data = !!c->pisrc->size; const size_t avail = c->pisrc->size; if (avail) { const ssize_t sent = splice(c->pisrc->read, NULL, out, NULL, avail, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); if (sent == -1) { if (would_block(errno)) { // short (zero) write can_write = false; goto decide; } else { redsocks_log_errno(&pump->c, pipeprio(pump, out), "splice(to %s)", pipename(pump, out)); redsocks_drop_client(&pump->c); return; } } else { c->pisrc->size -= sent; if (avail == sent) { has_data = false; } else { // short write can_write = false; goto decide; } } } } while (0); decide: if (!has_data && (*c->shut_src & EV_READ) && !(*c->shut_dst & EV_WRITE)) { if (shutdown(out, SHUT_WR) != 0) { redsocks_log_errno(&pump->c, LOG_ERR, "shutdown(%s, SHUT_WR)", pipename(pump, out)); } *c->shut_dst |= EV_WRITE; can_write = false; assert(!c->pisrc->size); redsocks_close(c->pisrc->read); c->pisrc->read = -1; redsocks_close(c->pisrc->write); c->pisrc->write = -1; if (shut_both(&pump->c)) { redsocks_drop_client(&pump->c); return; } } assert(!(can_write && has_data)); // incomplete write to writable socket if (!can_write && has_data) { if (event_pending(c->evsrc, EV_READ, NULL)) redsocks_log_error(&pump->c, LOG_DEBUG, "backpressure: event_del(%s_read)", pipename(pump, event_get_fd(c->evsrc))); redsocks_event_del(&pump->c, c->evsrc); redsocks_event_add(&pump->c, c->evdst); } else if (can_write && !has_data) { if (!event_pending(c->evsrc, EV_READ, NULL)) redsocks_log_error(&pump->c, LOG_DEBUG, "backpressure: event_add(%s_read)", pipename(pump, event_get_fd(c->evsrc))); redsocks_event_add(&pump->c, c->evsrc); redsocks_event_del(&pump->c, c->evdst); } else if (!can_write && !has_data) { // something like EOF redsocks_event_del(&pump->c, c->evsrc); redsocks_event_del(&pump->c, c->evdst); } } typedef struct redsplice_read_ctx_t { splice_pipe *dst; struct event *evsrc; struct event *evdst; evshut_t *shut_src; } redsplice_read_ctx; static void redsplice_read_cb(redsocks_pump *pump, redsplice_read_ctx *c, int in) { const size_t pipesize = 1048576; // some default value from fs.pipe-max-size const ssize_t got = splice(in, NULL, c->dst->write, NULL, pipesize, SPLICE_F_MOVE|SPLICE_F_NONBLOCK); if (got == -1) { if (would_block(errno)) { // there is data at `in', but pipe is full if (!event_pending(c->evsrc, EV_READ, NULL)) redsocks_log_error(&pump->c, LOG_DEBUG, "backpressure: event_del(%s_read)", pipename(pump, event_get_fd(c->evsrc))); redsocks_event_del(&pump->c, c->evsrc); } else { redsocks_log_errno(&pump->c, pipeprio(pump, in), "splice(from %s)", pipename(pump, in)); redsocks_drop_client(&pump->c); } return; } if (got == 0) { // got EOF if (shutdown(in, SHUT_RD) != 0) { if (errno != ENOTCONN) { // do not log common case for splice() redsocks_log_errno(&pump->c, LOG_DEBUG, "shutdown(%s, SHUT_RD) after EOF", pipename(pump, in)); } } *c->shut_src |= EV_READ; redsocks_event_del(&pump->c, c->evsrc); } else { c->dst->size += got; } event_active(c->evdst, EV_WRITE, 0); } static void redsocks_touch_pump(redsocks_pump *pump) { redsocks_touch_client(&pump->c); bufferevent_free_unused(&pump->c.client); bufferevent_free_unused(&pump->c.relay); } static void redsplice_relay_read(int fd, short what, void *_pump) { redsocks_pump *pump = _pump; assert(fd == event_get_fd(&pump->relay_read) && (what & EV_READ)); redsocks_touch_pump(pump); redsplice_read_ctx c = { .dst = &pump->reply, .evsrc = &pump->relay_read, .evdst = &pump->client_write, .shut_src = &pump->c.relay_evshut, }; redsplice_read_cb(pump, &c, fd); } static void redsplice_client_read(int fd, short what, void *_pump) { redsocks_pump *pump = _pump; assert(fd == event_get_fd(&pump->client_read) && (what & EV_READ)); redsocks_touch_pump(pump); redsplice_read_ctx c = { .dst = &pump->request, .evsrc = &pump->client_read, .evdst = &pump->relay_write, .shut_src = &pump->c.client_evshut, }; redsplice_read_cb(pump, &c, fd); } static void redsplice_relay_write(int fd, short what, void *_pump) { redsocks_pump *pump = _pump; assert(fd == event_get_fd(&pump->relay_write) && (what & EV_WRITE)); redsocks_touch_pump(pump); redsplice_write_ctx c = { .ebsrc = { pump->c.relay ? pump->c.relay->output : NULL, pump->c.client ? pump->c.client->input : NULL, }, .pisrc = &pump->request, .evsrc = &pump->client_read, .evdst = &pump->relay_write, .shut_src = &pump->c.client_evshut, .shut_dst = &pump->c.relay_evshut, }; redsplice_write_cb(pump, &c, fd); } static void redsplice_client_write(int fd, short what, void *_pump) { redsocks_pump *pump = _pump; assert(fd == event_get_fd(&pump->client_write) && (what & EV_WRITE)); redsocks_touch_pump(pump); redsplice_write_ctx c = { .ebsrc = { pump->c.client ? pump->c.client->output : NULL, pump->c.relay ? pump->c.relay->input : NULL, }, .pisrc = &pump->reply, .evsrc = &pump->relay_read, .evdst = &pump->client_write, .shut_src = &pump->c.relay_evshut, .shut_dst = &pump->c.client_evshut, }; redsplice_write_cb(pump, &c, fd); } static int redsocks_start_splicepump(redsocks_client *client) { int error = bufferevent_disable(client->client, EV_READ|EV_WRITE); if (!error) error = bufferevent_disable(client->relay, EV_READ|EV_WRITE); if (error) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return error; } // going to steal this buffers to the socket evbuffer_unfreeze(client->client->input, 0); evbuffer_unfreeze(client->client->output, 1); evbuffer_unfreeze(client->relay->input, 0); evbuffer_unfreeze(client->relay->output, 1); redsocks_pump *pump = red_pump(client); if (!error) error = pipe2(&pump->request.read, O_NONBLOCK); if (!error) error = pipe2(&pump->reply.read, O_NONBLOCK); if (error) { redsocks_log_errno(client, LOG_ERR, "pipe2"); return error; } struct event_base *base = NULL; const int relay_fd = bufferevent_getfd(client->relay); const int client_fd = bufferevent_getfd(client->client); if (!error) error = event_assign(&pump->client_read, base, client_fd, EV_READ|EV_PERSIST, redsplice_client_read, pump); if (!error) error = event_assign(&pump->client_write, base, client_fd, EV_WRITE|EV_PERSIST, redsplice_client_write, pump); if (!error) error = event_assign(&pump->relay_read, base, relay_fd, EV_READ|EV_PERSIST, redsplice_relay_read, pump); if (!error) error = event_assign(&pump->relay_write, base, relay_fd, EV_WRITE|EV_PERSIST, redsplice_relay_write, pump); if (error) { redsocks_log_errno(client, LOG_ERR, "event_assign"); return error; } redsocks_bufferevent_dropfd(client, client->relay); redsocks_bufferevent_dropfd(client, client->client); // flush buffers (if any) and enable EV_READ callbacks event_active(&pump->client_write, EV_WRITE, 0); event_active(&pump->relay_write, EV_WRITE, 0); redsocks_event_add(&pump->c, &pump->client_read); redsocks_event_add(&pump->c, &pump->relay_read); return 0; } static bool has_loopback_destination(redsocks_client *client) { const uint32_t net = ntohl(client->destaddr.sin_addr.s_addr) >> 24; return 0 == memcmp(&client->destaddr.sin_addr, &client->instance->config.relayaddr.sin_addr, sizeof(client->destaddr.sin_addr)) || net == 127 || net == 0; } void redsocks_drop_client(redsocks_client *client) { if (shut_both(client)) { redsocks_log_error(client, LOG_INFO, "connection closed"); } else { if (has_loopback_destination(client)) { static time_t last = 0; const time_t now = redsocks_time(NULL); if (now - last >= 3600) { // log this warning once an hour to save some debugging time, OTOH it may be valid traffic in some cases redsocks_log_error(client, LOG_NOTICE, "client tries to connect to the proxy using proxy! Usual proxy security policy is to drop alike connection"); last = now; } } struct timeval now, idle; redsocks_gettimeofday(&now); // FIXME: use CLOCK_MONOTONIC timersub(&now, &client->last_event, &idle); redsocks_log_error(client, LOG_INFO, "dropping client (%s), relay (%s), idle %ld.%06lds", redsocks_event_str( (~client->client_evshut) & (EV_READ|EV_WRITE) ), redsocks_event_str( (~client->relay_evshut) & (EV_READ|EV_WRITE) ), idle.tv_sec, idle.tv_usec); } if (client->instance->relay_ss->fini) client->instance->relay_ss->fini(client); if (client->client) redsocks_bufferevent_free(client->client); if (client->relay) redsocks_bufferevent_free(client->relay); if (client->instance->config.use_splice) { redsocks_pump *pump = red_pump(client); if (pump->request.read != -1) redsocks_close(pump->request.read); if (pump->request.write != -1) redsocks_close(pump->request.write); if (pump->reply.read != -1) redsocks_close(pump->reply.read); if (pump->reply.write != -1) redsocks_close(pump->reply.write); // redsocks_close MAY log error if some of events was not properly initialized int fd = -1; if (event_initialized(&pump->client_read)) { fd = event_get_fd(&pump->client_read); redsocks_event_del(&pump->c, &pump->client_read); } if (event_initialized(&pump->client_write)) { redsocks_event_del(&pump->c, &pump->client_write); } if (fd != -1) redsocks_close(fd); fd = -1; if (event_initialized(&pump->relay_read)) { fd = event_get_fd(&pump->relay_read); redsocks_event_del(&pump->c, &pump->relay_read); } if (event_initialized(&pump->relay_write)) { redsocks_event_del(&pump->c, &pump->relay_write); } if (fd != -1) { redsocks_close(fd); } } redsocks_conn_list_del(client); free(client); } void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how) { short evhow = 0; const char *strev, *strhow = NULL, *strevhow = NULL; unsigned short *pevshut; assert(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR); assert(buffev == client->client || buffev == client->relay); assert(event_get_fd(&buffev->ev_read) == event_get_fd(&buffev->ev_write)); if (how == SHUT_RD) { strhow = "SHUT_RD"; evhow = EV_READ; strevhow = "EV_READ"; } else if (how == SHUT_WR) { strhow = "SHUT_WR"; evhow = EV_WRITE; strevhow = "EV_WRITE"; } else if (how == SHUT_RDWR) { strhow = "SHUT_RDWR"; evhow = EV_READ|EV_WRITE; strevhow = "EV_READ|EV_WRITE"; } assert(strhow && strevhow); strev = bufname(client, buffev); pevshut = buffev == client->client ? &client->client_evshut : &client->relay_evshut; // if EV_WRITE is already shut and we're going to shutdown read then // we're either going to abort data flow (bad behaviour) or confirm EOF // and in this case socket is already SHUT_RD'ed if ( !(how == SHUT_RD && (*pevshut & EV_WRITE)) ) { if (shutdown(event_get_fd(&buffev->ev_read), how) != 0) redsocks_log_errno(client, LOG_ERR, "shutdown(%s, %s)", strev, strhow); } else { redsocks_log_error(client, LOG_DEBUG, "ignored shutdown(%s, %s)", strev, strhow); } redsocks_log_error(client, LOG_DEBUG, "shutdown: bufferevent_disable(%s, %s)", strev, strevhow); if (bufferevent_disable(buffev, evhow) != 0) redsocks_log_errno(client, LOG_ERR, "bufferevent_disable(%s, %s)", strev, strevhow); *pevshut |= evhow; if (shut_both(client)) { redsocks_log_error(client, LOG_DEBUG, "both client and server disconnected"); redsocks_drop_client(client); } } // I assume that -1 is invalid errno value static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev) { int pseudo_errno = red_socket_geterrno(buffev); if (pseudo_errno == -1) { redsocks_log_errno(client, LOG_ERR, "red_socket_geterrno"); return -1; } return pseudo_errno; } static void redsocks_event_error(struct bufferevent *buffev, short what, void *_arg) { redsocks_client *client = _arg; assert(buffev == client->relay || buffev == client->client); const int bakerrno = errno; redsocks_touch_client(client); if (what == (EVBUFFER_READ|EVBUFFER_EOF)) { struct bufferevent *antiev; if (buffev == client->relay) antiev = client->client; else antiev = client->relay; redsocks_shutdown(client, buffev, SHUT_RD); // If the client has already sent EOF and the pump is not active // (relay is activating), the code should not shutdown write-pipe. if (client->state == pump_active && antiev != NULL && evbuffer_get_length(antiev->output) == 0) redsocks_shutdown(client, antiev, SHUT_WR); } else { const int sockrrno = redsocks_socket_geterrno(client, buffev); const char *errsrc = ""; if (sockrrno != -1 && sockrrno != 0) { errno = sockrrno; errsrc = "socket "; } else { errno = bakerrno; } redsocks_log_errno(client, bufprio(client, buffev), "%s %serror, code " event_fmt_str, bufname(client, buffev), errsrc, event_fmt(what)); redsocks_drop_client(client); } } int sizes_equal(size_t a, size_t b) { return a == b; } int sizes_greater_equal(size_t a, size_t b) { return a >= b; } int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected) { size_t len = evbuffer_get_length(input); if (comparator(len, expected)) { int read = evbuffer_remove(input, data, expected); UNUSED(read); assert(read == expected); return 0; } else { redsocks_log_error(client, LOG_NOTICE, "Can't get expected amount of data"); redsocks_drop_client(client); return -1; } } struct evbuffer *mkevbuffer(void *data, size_t len) { struct evbuffer *buff = NULL, *retval = NULL; buff = evbuffer_new(); if (!buff) { log_errno(LOG_ERR, "evbuffer_new"); goto fail; } if (evbuffer_add(buff, data, len) < 0) { log_errno(LOG_ERR, "evbuffer_add"); goto fail; } retval = buff; buff = NULL; fail: if (buff) evbuffer_free(buff); return retval; } int redsocks_write_helper_ex( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high) { assert(client); return redsocks_write_helper_ex_plain(buffev, client, (redsocks_message_maker_plain)mkmessage, client, state, wm_low, wm_high); } int redsocks_write_helper_ex_plain( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker_plain mkmessage, void *p, int state, size_t wm_low, size_t wm_high) { int len; struct evbuffer *buff = NULL; int drop = 1; if (mkmessage) { buff = mkmessage(p); if (!buff) goto fail; assert(!client || buffev == client->relay); len = bufferevent_write_buffer(buffev, buff); if (len < 0) { if (client) redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); else log_errno(LOG_ERR, "bufferevent_write_buffer"); goto fail; } } if (client) client->state = state; bufferevent_setwatermark(buffev, EV_READ, wm_low, wm_high); bufferevent_enable(buffev, EV_READ); drop = 0; fail: if (buff) evbuffer_free(buff); if (drop && client) redsocks_drop_client(client); return drop ? -1 : 0; } int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only) { assert(client); return redsocks_write_helper_ex(buffev, client, mkmessage, state, wm_only, wm_only); } static void redsocks_relay_connected(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(buffev == client->relay); redsocks_touch_client(client); if (!red_is_socket_connected_ok(buffev)) { redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); goto fail; } client->relay->readcb = client->instance->relay_ss->readcb; client->relay->writecb = client->instance->relay_ss->writecb; client->relay->writecb(buffev, _arg); return; fail: redsocks_drop_client(client); } void redsocks_connect_relay(redsocks_client *client) { client->relay = red_connect_relay(&client->instance->config.relayaddr, redsocks_relay_connected, redsocks_event_error, client); if (!client->relay) { redsocks_log_errno(client, LOG_ERR, "red_connect_relay"); redsocks_drop_client(client); } } static struct timeval drop_idle_connections() { assert(connpres_idle_timeout() > 0); struct timeval now, zero, max_idle, best_next; gettimeofday(&now, NULL); // FIXME: use CLOCK_MONOTONIC timerclear(&zero); timerclear(&max_idle); max_idle.tv_sec = connpres_idle_timeout(); best_next = max_idle; redsocks_instance *instance; list_for_each_entry(instance, &instances, list) { redsocks_client *tmp, *client; list_for_each_entry_safe(client, tmp, &instance->clients, list) { struct timeval idle; timersub(&now, &client->last_event, &idle); if (timercmp(&idle, &zero, <=) || timercmp(&max_idle, &idle, <=)) { redsocks_drop_client(client); best_next = zero; } else { struct timeval delay; timersub(&max_idle, &idle, &delay); if (timercmp(&delay, &best_next, <)) { best_next = delay; } } } } return best_next; } static bool conn_pressure_ongoing() { if (redsocks_conn >= redsocks_conn_max()) return true; int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) return true; close(fd); return false; } static void conn_pressure() { struct timeval next; timerclear(&next); if (connpres_idle_timeout()) { next = drop_idle_connections(); if (!timerisset(&next)) { log_error(LOG_WARNING, "dropped connections idle for %d+ seconds", connpres_idle_timeout()); return; // pressure solved } } accept_backoff_ms = (accept_backoff_ms << 1) + 1; clamp_value(accept_backoff_ms, 1, max_accept_backoff_ms()); uint32_t delay = (red_randui32() % accept_backoff_ms) + 1; struct timeval tvdelay = { delay / 1000, (delay % 1000) * 1000 }; if (timerisset(&next) && timercmp(&next, &tvdelay, <) ) { tvdelay = next; } log_error(LOG_WARNING, "accept: backing off for %ld.%06lds", tvdelay.tv_sec, tvdelay.tv_usec); if (event_add(&accept_backoff_ev, &tvdelay) != 0) log_errno(LOG_ERR, "event_add"); redsocks_instance *self = NULL; list_for_each_entry(self, &instances, list) { if (event_del(&self->listener) != 0) log_errno(LOG_ERR, "event_del"); } } // if there are no idle connections delay to the nearest one is returned static void accept_enable() { redsocks_instance *self = NULL; list_for_each_entry(self, &instances, list) { if (event_add(&self->listener, NULL) != 0) log_errno(LOG_ERR, "event_add"); } } static void conn_pressure_lowered() { if (redsocks_conn >= redsocks_conn_max()) return; // lowered... not so much! if (event_pending(&accept_backoff_ev, EV_TIMEOUT, NULL)) { if (event_del(&accept_backoff_ev) != 0) log_errno(LOG_ERR, "event_del"); accept_enable(); } } static void redsocks_accept_backoff(int fd, short what, void *_null) { if (conn_pressure_ongoing()) { conn_pressure(); // rearm timeout } else { accept_enable(); // `accept_backoff_ev` is not pending now } } void redsocks_close_internal(int fd, const char* file, int line, const char *func) { if (close(fd) == 0) { conn_pressure_lowered(); } else { const int do_errno = 1; _log_write(file, line, func, do_errno, LOG_WARNING, "close"); } } void redsocks_event_add_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func) { if (event_add(ev, NULL) != 0) { const int do_errno = 1; redsocks_log_write_plain(file, line, func, do_errno, &(client)->clientaddr, &(client)->destaddr, LOG_WARNING, "event_add"); } } void redsocks_event_del_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func) { if (event_del(ev) != 0) { const int do_errno = 1; redsocks_log_write_plain(file, line, func, do_errno, &(client)->clientaddr, &(client)->destaddr, LOG_WARNING, "event_del"); } } void redsocks_bufferevent_dropfd_internal(redsocks_client *client, struct bufferevent *ev, const char *file, int line, const char *func) { if (bufferevent_setfd(ev, -1) != 0) { const int do_errno = 1; redsocks_log_write_plain(file, line, func, do_errno, &(client)->clientaddr, &(client)->destaddr, LOG_WARNING, "bufferevent_setfd"); } } void redsocks_bufferevent_free(struct bufferevent *buffev) { int fd = bufferevent_getfd(buffev); if (bufferevent_setfd(buffev, -1)) { // to avoid EBADFD warnings from epoll log_errno(LOG_WARNING, "bufferevent_setfd"); } bufferevent_free(buffev); if (fd != -1) redsocks_close(fd); } static void redsocks_conn_list_add(redsocks_instance *self, redsocks_client *client) { assert(list_empty(&client->list)); assert(redsocks_conn < redsocks_conn_max()); list_add(&client->list, &self->clients); redsocks_conn++; if (redsocks_conn >= redsocks_conn_max()) { log_error(LOG_WARNING, "reached redsocks_conn_max limit, %d connections", redsocks_conn); conn_pressure(); } } static void redsocks_conn_list_del(redsocks_client *client) { if (!list_empty(&client->list)) { redsocks_conn--; list_del(&client->list); } conn_pressure_lowered(); } static void redsocks_accept_client(int fd, short what, void *_arg) { redsocks_instance *self = _arg; redsocks_client *client = NULL; struct sockaddr_in clientaddr; struct sockaddr_in myaddr; struct sockaddr_in destaddr; socklen_t addrlen = sizeof(clientaddr); int client_fd = -1; int error; assert(redsocks_conn < redsocks_conn_max()); // working with client_fd client_fd = accept(fd, (struct sockaddr*)&clientaddr, &addrlen); if (client_fd == -1) { const int e = errno; log_errno(LOG_WARNING, "accept"); /* Different systems use different `errno` value to signal different * `lack of file descriptors` conditions. Here are most of them. */ if (e == ENFILE || e == EMFILE || e == ENOBUFS || e == ENOMEM) { conn_pressure(); } goto fail; } accept_backoff_ms = 0; // socket is really bound now (it could be bound to 0.0.0.0) addrlen = sizeof(myaddr); error = getsockname(client_fd, (struct sockaddr*)&myaddr, &addrlen); if (error) { log_errno(LOG_WARNING, "getsockname"); goto fail; } error = getdestaddr(client_fd, &clientaddr, &myaddr, &destaddr); if (error) { goto fail; } error = fcntl_nonblock(client_fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } if (apply_tcp_keepalive(client_fd)) goto fail; // everything seems to be ok, let's allocate some memory client = calloc(1, sizeof_client(self)); if (!client) { log_errno(LOG_ERR, "calloc"); goto fail; } client->instance = self; if (client->instance->config.use_splice) { redsocks_pump *pump = red_pump(client); pump->request.read = -1; pump->request.write = -1; pump->reply.read = -1; pump->reply.write = -1; } memcpy(&client->clientaddr, &clientaddr, sizeof(clientaddr)); memcpy(&client->destaddr, &destaddr, sizeof(destaddr)); INIT_LIST_HEAD(&client->list); self->relay_ss->init(client); if (redsocks_gettimeofday(&client->first_event) != 0) goto fail; redsocks_touch_client(client); client->client = bufferevent_new(client_fd, NULL, NULL, redsocks_event_error, client); if (!client->client) { log_errno(LOG_ERR, "bufferevent_new"); goto fail; } client_fd = -1; redsocks_conn_list_add(self, client); // enable reading to handle EOF from client if (bufferevent_enable(client->client, EV_READ) != 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); goto fail; } redsocks_log_error(client, LOG_INFO, "accepted"); if (self->relay_ss->connect_relay) self->relay_ss->connect_relay(client); else redsocks_connect_relay(client); return; fail: if (client) { redsocks_drop_client(client); } if (client_fd != -1) redsocks_close(client_fd); } static const char *redsocks_evshut_str(unsigned short evshut) { return evshut == EV_READ ? "SHUT_RD" : evshut == EV_WRITE ? "SHUT_WR" : evshut == (EV_READ|EV_WRITE) ? "SHUT_RDWR" : evshut == 0 ? "" : "???"; } static const char *redsocks_event_str(unsigned short what) { return what == EV_READ ? "R/-" : what == EV_WRITE ? "-/W" : what == (EV_READ|EV_WRITE) ? "R/W" : what == 0 ? "-/-" : "???"; } static uint32_t redsocks_debug_dump_instance(redsocks_instance *instance, struct timeval *now) { redsocks_client *client = NULL; uint32_t conn = 0; char bindaddr_str[RED_INET_ADDRSTRLEN]; log_error(LOG_NOTICE, "Dumping client list for %s at %s:", instance->config.type, red_inet_ntop(&instance->config.bindaddr, bindaddr_str, sizeof(bindaddr_str))); list_for_each_entry(client, &instance->clients, list) { conn++; const char *s_client_evshut = redsocks_evshut_str(client->client_evshut); const char *s_relay_evshut = redsocks_evshut_str(client->relay_evshut); struct timeval age, idle; timersub(now, &client->first_event, &age); timersub(now, &client->last_event, &idle); if (!instance->config.use_splice) { redsocks_log_error(client, LOG_NOTICE, "client: %i (%s)%s%s, relay: %i (%s)%s%s, age: %ld.%06ld sec, idle: %ld.%06ld sec.", client->client ? bufferevent_getfd(client->client) : -1, client->client ? redsocks_event_str(client->client->enabled) : "NULL", s_client_evshut[0] ? " " : "", s_client_evshut, client->relay ? bufferevent_getfd(client->relay) : -1, client->relay ? redsocks_event_str(client->relay->enabled) : "NULL", s_relay_evshut[0] ? " " : "", s_relay_evshut, age.tv_sec, age.tv_usec, idle.tv_sec, idle.tv_usec); } else { redsocks_pump *pump = red_pump(client); redsocks_log_error(client, LOG_NOTICE, "client: buf %i (%s) splice %i (%s/%s) pipe[%d, %d]=%zu%s%s, relay: buf %i (%s) splice %i (%s/%s) pipe[%d, %d]=%zu%s%s, age: %ld.%06ld sec, idle: %ld.%06ld sec.", client->client ? bufferevent_getfd(client->client) : -1, client->client ? redsocks_event_str(client->client->enabled) : "NULL", event_get_fd(&pump->client_read), event_pending(&pump->client_read, EV_READ, NULL) ? "R" : "-", event_pending(&pump->client_write, EV_WRITE, NULL) ? "W" : "-", pump->request.read, pump->request.write, pump->request.size, s_client_evshut[0] ? " " : "", s_client_evshut, client->relay ? bufferevent_getfd(client->relay) : -1, client->relay ? redsocks_event_str(client->relay->enabled) : "NULL", event_get_fd(&pump->relay_read), event_pending(&pump->relay_read, EV_READ, NULL) ? "R" : "-", event_pending(&pump->relay_write, EV_WRITE, NULL) ? "W" : "-", pump->reply.read, pump->reply.write, pump->reply.size, s_relay_evshut[0] ? " " : "", s_relay_evshut, age.tv_sec, age.tv_usec, idle.tv_sec, idle.tv_usec); } } log_error(LOG_NOTICE, "End of client list. %d clients.", conn); return conn; } static void redsocks_debug_dump(int sig, short what, void *_arg) { redsocks_instance *instance = NULL; struct timeval now; redsocks_gettimeofday(&now); uint32_t conn = 0; list_for_each_entry(instance, &instances, list) conn += redsocks_debug_dump_instance(instance, &now); assert(conn == redsocks_conn); } bool redsocks_has_splice_instance() { // only i->config is initialised at the moment redsocks_instance *i = NULL; list_for_each_entry(i, &instances, list) { if (i->config.use_splice) return true; } return false; } static void redsocks_fini_instance(redsocks_instance *instance); static int redsocks_init_instance(redsocks_instance *instance) { /* FIXME: redsocks_fini_instance is called in case of failure, this * function will remove instance from instances list - result * looks ugly. */ int error; int fd = -1; fd = red_socket_server(SOCK_STREAM, &instance->config.bindaddr); if (fd == -1) { goto fail; } error = listen(fd, instance->config.listenq); if (error) { log_errno(LOG_ERR, "listen"); goto fail; } event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redsocks_accept_client, instance); fd = -1; error = event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } if (instance->relay_ss->instance_init) instance->relay_ss->instance_init(instance); return 0; fail: redsocks_fini_instance(instance); if (fd != -1) { redsocks_close(fd); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void redsocks_fini_instance(redsocks_instance *instance) { if (!list_empty(&instance->clients)) { redsocks_client *tmp, *client = NULL; log_error(LOG_WARNING, "There are connected clients during shutdown! Disconnecting them."); list_for_each_entry_safe(client, tmp, &instance->clients, list) { redsocks_drop_client(client); } } if (instance->relay_ss->instance_fini) instance->relay_ss->instance_fini(instance); if (event_initialized(&instance->listener)) { if (event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); redsocks_close(event_get_fd(&instance->listener)); memset(&instance->listener, 0, sizeof(instance->listener)); } list_del(&instance->list); free(instance->config.type); free(instance->config.login); free(instance->config.password); memset(instance, 0, sizeof(*instance)); free(instance); } static int redsocks_fini(); static struct event debug_dumper; static int redsocks_init() { struct sigaction sa = { }, sa_old = { }; redsocks_instance *tmp, *instance = NULL; redsocks_conn = 0; sa.sa_handler = SIG_IGN; sa.sa_flags = SA_RESTART; if (sigaction(SIGPIPE, &sa, &sa_old) == -1) { log_errno(LOG_ERR, "sigaction"); return -1; } signal_set(&debug_dumper, SIGUSR1, redsocks_debug_dump, NULL); if (signal_add(&debug_dumper, NULL) != 0) { log_errno(LOG_ERR, "signal_add"); goto fail; } event_set(&accept_backoff_ev, -1, 0, redsocks_accept_backoff, NULL); list_for_each_entry_safe(instance, tmp, &instances, list) { if (redsocks_init_instance(instance) != 0) goto fail; } return 0; fail: // that was the first resource allocation, it return's on failure, not goto-fail's sigaction(SIGPIPE, &sa_old, NULL); redsocks_fini(); return -1; } static int redsocks_fini() { redsocks_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) redsocks_fini_instance(instance); assert(redsocks_conn == 0); if (signal_initialized(&debug_dumper)) { if (signal_del(&debug_dumper) != 0) log_errno(LOG_WARNING, "signal_del"); memset(&debug_dumper, 0, sizeof(debug_dumper)); } return 0; } app_subsys redsocks_subsys = { .init = redsocks_init, .fini = redsocks_fini, .conf_section = &redsocks_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */