redsocks-release-0.5/0000755000175000017500000000000013030520742014501 5ustar apoikosapoikosredsocks-release-0.5/socks5.c0000644000175000017500000002347213030520742016064 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "utils.h" #include "log.h" #include "redsocks.h" #include "socks5.h" typedef enum socks5_state_t { socks5_new, socks5_method_sent, socks5_auth_sent, socks5_request_sent, socks5_skip_domain, socks5_skip_address, socks5_MAX, } socks5_state; typedef struct socks5_client_t { int do_password; // 1 - password authentication is possible int to_skip; // valid while reading last reply (after main request) } socks5_client; const char *socks5_strstatus[] = { "ok", "server failure", "connection not allowed by ruleset", "network unreachable", "host unreachable", "connection refused", "TTL expired", "command not supported", "address type not supported", }; const size_t socks5_strstatus_len = SIZEOF_ARRAY(socks5_strstatus); const char* socks5_status_to_str(int socks5_status) { if (0 <= socks5_status && socks5_status < socks5_strstatus_len) { return socks5_strstatus[socks5_status]; } else { return ""; } } bool socks5_is_valid_cred(const char *login, const char *password) { if (!login || !password) return false; if (strlen(login) > 255) { log_error(LOG_WARNING, "Socks5 login can't be more than 255 chars, <%s> is too long", login); return false; } if (strlen(password) > 255) { log_error(LOG_WARNING, "Socks5 password can't be more than 255 chars, <%s> is too long", password); return false; } return true; } static void socks5_instance_init(redsocks_instance *instance) { redsocks_config *config = &instance->config; if (config->login || config->password) { bool deauth = false; if (config->login && config->password) { deauth = ! socks5_is_valid_cred(config->login, config->password); } else { log_error(LOG_WARNING, "Socks5 needs either both login and password or none of them"); deauth = true; } if (deauth) { free(config->login); free(config->password); config->login = config->password = NULL; } } } static void socks5_client_init(redsocks_client *client) { socks5_client *socks5 = red_payload(client); const redsocks_config *config = &client->instance->config; client->state = socks5_new; socks5->do_password = (config->login && config->password) ? 1 : 0; } static struct evbuffer *socks5_mkmethods(redsocks_client *client) { socks5_client *socks5 = red_payload(client); return socks5_mkmethods_plain(socks5->do_password); } struct evbuffer *socks5_mkmethods_plain(int do_password) { assert(do_password == 0 || do_password == 1); int len = sizeof(socks5_method_req) + do_password; socks5_method_req *req = calloc(1, len); req->ver = socks5_ver; req->num_methods = 1 + do_password; req->methods[0] = socks5_auth_none; if (do_password) req->methods[1] = socks5_auth_password; struct evbuffer *ret = mkevbuffer(req, len); free(req); return ret; } static struct evbuffer *socks5_mkpassword(redsocks_client *client) { return socks5_mkpassword_plain(client->instance->config.login, client->instance->config.password); } struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password) { size_t ulen = strlen(login); size_t plen = strlen(password); size_t length = 1 /* version */ + 1 + ulen + 1 + plen; uint8_t req[length]; req[0] = socks5_password_ver; // RFC 1929 says so req[1] = ulen; memcpy(&req[2], login, ulen); req[2+ulen] = plen; memcpy(&req[3+ulen], password, plen); return mkevbuffer(req, length); } struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr) { struct { socks5_req head; socks5_addr_ipv4 ip; } PACKED req; assert(destaddr->sin_family == AF_INET); req.head.ver = socks5_ver; req.head.cmd = socks5_cmd; req.head.reserved = 0; req.head.addrtype = socks5_addrtype_ipv4; req.ip.addr = destaddr->sin_addr.s_addr; req.ip.port = destaddr->sin_port; return mkevbuffer(&req, sizeof(req)); } static struct evbuffer *socks5_mkconnect(redsocks_client *client) { return socks5_mkcommand_plain(socks5_cmd_connect, &client->destaddr); } static void socks5_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == socks5_new) { redsocks_write_helper( buffev, client, socks5_mkmethods, socks5_method_sent, sizeof(socks5_method_reply) ); } } const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password) { if (reply->ver != socks5_ver) return "Socks5 server reported unexpected auth methods reply version..."; else if (reply->method == socks5_auth_invalid) return "Socks5 server refused all our auth methods."; else if (reply->method != socks5_auth_none && !(reply->method == socks5_auth_password && do_password)) return "Socks5 server requested unexpected auth method..."; else return NULL; } static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_method_reply reply; const char *error = NULL; if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) return; error = socks5_is_known_auth_method(&reply, socks5->do_password); if (error) { redsocks_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); redsocks_drop_client(client); } else if (reply.method == socks5_auth_none) { redsocks_write_helper( buffev, client, socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) ); } else if (reply.method == socks5_auth_password) { redsocks_write_helper( buffev, client, socks5_mkpassword, socks5_auth_sent, sizeof(socks5_auth_reply) ); } } static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_auth_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_password_ver) { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected auth reply version %d", reply.ver); redsocks_drop_client(client); } else if (reply.status == socks5_password_passed) redsocks_write_helper( buffev, client, socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) ); else { redsocks_log_error(client, LOG_NOTICE, "Socks5 auth failure, status %i", reply.status); redsocks_drop_client(client); } } static void socks5_read_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) { socks5_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) return; if (reply.ver != socks5_ver) { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version..."); redsocks_drop_client(client); } else if (reply.status == socks5_status_succeeded) { socks5_state nextstate; size_t len; if (reply.addrtype == socks5_addrtype_ipv4) { len = socks5->to_skip = sizeof(socks5_addr_ipv4); nextstate = socks5_skip_address; } else if (reply.addrtype == socks5_addrtype_ipv6) { len = socks5->to_skip = sizeof(socks5_addr_ipv6); nextstate = socks5_skip_address; } else if (reply.addrtype == socks5_addrtype_domain) { socks5_addr_domain domain; len = sizeof(domain.size); nextstate = socks5_skip_domain; } else { redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type..."); redsocks_drop_client(client); return; } redsocks_write_helper( buffev, client, NULL, nextstate, len ); } else { redsocks_log_error(client, LOG_NOTICE, "Socks5 server status: %s (%i)", /* 0 <= reply.status && */ reply.status < SIZEOF_ARRAY(socks5_strstatus) ? socks5_strstatus[reply.status] : "?", reply.status); redsocks_drop_client(client); } } static void socks5_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; socks5_client *socks5 = red_payload(client); redsocks_touch_client(client); if (client->state == socks5_method_sent) { socks5_read_auth_methods(buffev, client, socks5); } else if (client->state == socks5_auth_sent) { socks5_read_auth_reply(buffev, client, socks5); } else if (client->state == socks5_request_sent) { socks5_read_reply(buffev, client, socks5); } else if (client->state == socks5_skip_domain) { socks5_addr_ipv4 ipv4; // all socks5_addr*.port are equal uint8_t size; if (redsocks_read_expected(client, buffev->input, &size, sizes_greater_equal, sizeof(size)) < 0) return; socks5->to_skip = size + sizeof(ipv4.port); redsocks_write_helper( buffev, client, NULL, socks5_skip_address, socks5->to_skip ); } else if (client->state == socks5_skip_address) { uint8_t data[socks5->to_skip]; if (redsocks_read_expected(client, buffev->input, data, sizes_greater_equal, socks5->to_skip) < 0) return; redsocks_start_relay(client); } else { redsocks_drop_client(client); } } relay_subsys socks5_subsys = { .name = "socks5", .payload_len = sizeof(socks5_client), .instance_payload_len = 0, .readcb = socks5_read_cb, .writecb = socks5_write_cb, .init = socks5_client_init, .instance_init = socks5_instance_init, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/libc-compat.h0000644000175000017500000000171413030520742017047 0ustar apoikosapoikos#ifndef UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 #define UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 /* all these definitions, are included into bits/in.h from libc6-dev 2.15-0ubuntu10 * from Ubuntu 12.04 and is not included into libc6-dev 2.11.1-0ubuntu7.10 from * Ubuntu 10.04. * linux/in.h is not included directly because of lots of redefinitions, * extracting single value from linux/in.h is not done because it looks like * autotools reinvention */ #ifndef IP_ORIGDSTADDR # warning Using hardcoded value for IP_ORIGDSTADDR as libc headers do not define it. # define IP_ORIGDSTADDR 20 #endif #ifndef IP_RECVORIGDSTADDR # warning Using hardcoded value for IP_RECVORIGDSTADDR as libc headers do not define it. # define IP_RECVORIGDSTADDR IP_ORIGDSTADDR #endif #ifndef IP_TRANSPARENT # warning Using hardcoded value for IP_TRANSPARENT as libc headers do not define it. # define IP_TRANSPARENT 19 #endif #endif // 67C91670_FCCB_4855_BDF7_609F1EECB8B4 redsocks-release-0.5/debug.c0000644000175000017500000001030313030520742015730 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "parser.h" #include "main.h" #include "log.h" #include "utils.h" #include #include #include #include #include typedef struct debug_instance_t { int configured; char* http_ip; uint16_t http_port; struct evhttp* http_server; } debug_instance; static debug_instance instance = { .configured = 0, }; static parser_entry debug_entries[] = { { .key = "http_ip", .type = pt_pchar }, { .key = "http_port", .type = pt_uint16 }, { } }; static int debug_onenter(parser_section *section) { if (instance.configured) { parser_error(section->context, "only one instance of debug is valid"); return -1; } memset(&instance, 0, sizeof(instance)); for (parser_entry *entry = §ion->entries[0]; entry->key; entry++) entry->addr = (strcmp(entry->key, "http_ip") == 0) ? (void*)&instance.http_ip: (strcmp(entry->key, "http_port") == 0) ? (void*)&instance.http_port : NULL; return 0; } static int debug_onexit(parser_section *section) { instance.configured = 1; if (!instance.http_ip) instance.http_ip = strdup("localhost"); return 0; } static parser_section debug_conf_section = { .name = "debug", .entries = debug_entries, .onenter = debug_onenter, .onexit = debug_onexit, }; static void debug_pipe(struct evhttp_request *req, void *arg) { UNUSED(arg); int fds[2]; int code = (pipe(fds) == 0) ? HTTP_OK : HTTP_SERVUNAVAIL; evhttp_send_reply(req, code, NULL, NULL); } static void debug_meminfo_json(struct evhttp_request *req, void *arg) { UNUSED(arg); struct evbuffer* body = evbuffer_new(); evbuffer_add(body, "{", 1); FILE* fd = fopen("/proc/vmstat", "r"); while (!feof(fd)) { char buf[64]; size_t pages; if (fscanf(fd, "%63s %zu", buf, &pages) == 2 && strncmp(buf, "nr_", 3) == 0) { evbuffer_add_printf(body, "\"%s\": %zu, ", buf, pages); } for (int c = 0; c != EOF && c != '\n'; c = fgetc(fd)) ; } fclose(fd); size_t vmsize, rss, share, text, z, data; fd = fopen("/proc/self/statm", "r"); if (fscanf(fd, "%zu %zu %zu %zu %zu %zu %zu", &vmsize, &rss, &share, &text, &z, &data, &z) == 7) { evbuffer_add_printf(body, "\"vmsize\": %zu, \"vmrss\": %zu, \"share\": %zu, \"text\": %zu, \"data\": %zu, ", vmsize, rss, share, text, data); } fclose(fd); evbuffer_add_printf(body, "\"getpagesize\": %d}", getpagesize()); evhttp_send_reply(req, HTTP_OK, NULL, body); evbuffer_free(body); } static int debug_fini(); static int debug_init(struct event_base* evbase) { if (!instance.http_port) return 0; instance.http_server = evhttp_new(evbase); if (!instance.http_server) { log_errno(LOG_ERR, "evhttp_new()"); goto fail; } if (evhttp_bind_socket(instance.http_server, instance.http_ip, instance.http_port) != 0) { log_errno(LOG_ERR, "evhttp_bind_socket()"); goto fail; } if (evhttp_set_cb(instance.http_server, "/debug/pipe", debug_pipe, NULL) != 0) { log_errno(LOG_ERR, "evhttp_set_cb()"); goto fail; } if (evhttp_set_cb(instance.http_server, "/debug/meminfo.json", debug_meminfo_json, NULL) != 0) { log_errno(LOG_ERR, "evhttp_set_cb()"); goto fail; } return 0; fail: debug_fini(); return -1; } static int debug_fini() { if (instance.http_server) { evhttp_free(instance.http_server); instance.http_server = 0; } free(instance.http_ip); memset(&instance, 0, sizeof(instance)); return 0; } app_subsys debug_subsys = { .init = debug_init, .fini = debug_fini, .conf_section = &debug_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/parser.c0000644000175000017500000004031613030520742016145 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "utils.h" #include "parser.h" #include "log.h" #define FREE(ptr) do { free(ptr); ptr = NULL; } while (0) typedef int (*value_parser)(parser_context *context, void *addr, const char *token); struct parser_context_t { FILE *fd; parser_section *sections; int line; int error; struct { size_t size; size_t filled; char *data; } buffer; }; void parser_error(parser_context *context, const char *fmt, ...) { va_list ap; struct evbuffer *buff = evbuffer_new(); const char *msg; va_start(ap, fmt); if (buff) { evbuffer_add_vprintf(buff, fmt, ap); msg = (const char*)evbuffer_pullup(buff, -1); } else msg = error_lowmem; va_end(ap); context->error = 1; fprintf(stderr, "file parsing error at line %u: %s\n", context->line, msg); if (buff) evbuffer_free(buff); } parser_context* parser_start(FILE *fd) { parser_context *ret = calloc(1, sizeof(parser_context)); if (!ret) return NULL; ret->fd = fd; ret->buffer.size = 128; // should be big enough to fetch whole ``line`` ret->buffer.data = malloc(ret->buffer.size); if (!ret->buffer.data) { free(ret); return NULL; } return ret; } void parser_add_section(parser_context *context, parser_section *section) { section->next = context->sections; context->sections = section; section->context = context; } void parser_stop(parser_context *context) { free(context->buffer.data); free(context); } static char unescape(int c) { switch (c) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; case '\\': return '\\'; case '\'': return '\''; case '\"': return '\"'; default: return 0; } } /** returns NULL on invalid OR incomplete token */ static char *gettoken(parser_context *context, char **iter) { char *ret = NULL; size_t len = 0; enum { gt_cstr, gt_plainstr } copytype; // skip spaces while (**iter && isspace(**iter)) (*iter)++; if ( !**iter ) return NULL; // count strlen() of output buffer if ( **iter == '\"' ) { // string with escapes and spaces char *p = *iter + 1; copytype = gt_cstr; while ( 1 ) { if (*p == '\0') return NULL; if (*p == '\"') break; if (*p == '\\') { if ( p[1] != '\0') { if ( unescape(p[1]) ) p++; else { parser_error(context, "unknown escaped char after \\"); return NULL; } } else { return NULL; } } len++; p++; } } else if ( isdigit(**iter) ) { // integer OR IP/NETMASK char *p = *iter; copytype = gt_plainstr; while ( 1 ) { if ( *p == '\0' ) return NULL; else if ( isdigit(*p) || *p == '.' ) p++; else if ( *p == '/' ) { if (isdigit(p[1])) p++; else if (p[1] == '/' || p[1] == '*') // comment token is coming! break; else return NULL; } else break; } len = p - *iter; } else if ( isalpha(**iter) ) { // simple-string char *p = *iter; copytype = gt_plainstr; while ( 1 ) { if ( *p == '\0' ) return NULL; else if (isalnum(*p) || *p == '_' || *p == '.' || *p == '-') // for domain-names p++; else break; } len = p - *iter; } else if ( **iter == '{' || **iter == '}' || **iter == '=' || **iter == ';' ) { // config punctuation copytype = gt_plainstr; len = 1; } else if ( **iter == '/' && ( (*iter)[1] == '/' || (*iter)[1] == '*' ) ) { // comment-start copytype = gt_plainstr; len = 2; } else { parser_error(context, "unexpected char"); return NULL; } ret = malloc(len + 1); if (!ret) { parser_error(context, "malloc failed"); return NULL; } if (copytype == gt_cstr) { char *p = ret; (*iter)++; while ( 1 ) { if (**iter == '\"') { (*iter)++; break; } if (**iter == '\\') { *p = unescape(*(*iter + 1)); (*iter)++; } else { *p = **iter; } (*iter)++; p++; } *p = 0; } else if (copytype == gt_plainstr) { memcpy(ret, *iter, len); *iter += len; ret[len] = 0; } return ret; } static void context_filled_add(parser_context *context, int shift) { context->buffer.filled += shift; context->buffer.data[context->buffer.filled] = 0; } static void context_filled_set(parser_context *context, size_t val) { context->buffer.filled = val; context->buffer.data[context->buffer.filled] = 0; } static int vp_pbool(parser_context *context, void *addr, const char *token) { char *strtrue[] = { "ok", "on", "yes", "true" }; char *strfalse[] = { "off", "no", "false" }; char **tpl; FOREACH(tpl, strtrue) if (strcmp(token, *tpl) == 0) { *(bool*)addr = true; return 0; } FOREACH(tpl, strfalse) if (strcmp(token, *tpl) == 0) { *(bool*)addr = false; return 0; } parser_error(context, "boolean is not parsed"); return -1; } static int vp_disclose_src(parser_context *context, void *addr, const char *token) { enum disclose_src_e *dst = addr; struct { char *name; enum disclose_src_e value; } opt[] = { { "off", DISCLOSE_NONE }, { "no", DISCLOSE_NONE }, { "false", DISCLOSE_NONE }, { "X-Forwarded-For", DISCLOSE_X_FORWARDED_FOR }, { "Forwarded_ip", DISCLOSE_FORWARDED_IP }, { "Forwarded_ipport", DISCLOSE_FORWARDED_IPPORT }, }; for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) { if (strcmp(token, opt[i].name) == 0) { *dst = opt[i].value; return 0; } } parser_error(context, "disclose_src <%s> is not parsed", token); return -1; } static int vp_on_proxy_fail(parser_context *context, void *addr, const char *token) { enum on_proxy_fail_e *dst = addr; struct { char *name; enum on_proxy_fail_e value; } opt[] = { { "close", ONFAIL_CLOSE }, { "forward_http_err", ONFAIL_FORWARD_HTTP_ERR }, }; for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) { if (strcmp(token, opt[i].name) == 0) { *dst = opt[i].value; return 0; } } parser_error(context, "on_proxy_fail <%s> is not parsed", token); return -1; } static int vp_pchar(parser_context *context, void *addr, const char *token) { char *p = strdup(token); if (!p) { parser_error(context, "strdup failed"); return -1; } *(char**)addr = p; return 0; } static int vp_uint16(parser_context *context, void *addr, const char *token) { char *end; unsigned long int uli = strtoul(token, &end, 0); if (uli > 0xFFFF) { parser_error(context, "integer out of 16bit range"); return -1; } if (*end != '\0') { parser_error(context, "integer is not parsed"); return -1; } *(uint16_t*)addr = (uint16_t)uli; return 0; } static int vp_uint32(parser_context *context, void *addr, const char *token) { char *end; uint32_t uli = strtoul(token, &end, 0); if (*end != '\0') { parser_error(context, "integer is not parsed"); return -1; } *(uint32_t*)addr = uli; return 0; } static int vp_in_addr(parser_context *context, void *addr, const char *token) { struct in_addr ia; if (inet_aton(token, &ia)) { memcpy(addr, &ia, sizeof(ia)); } else { struct addrinfo *ainfo, hints; int err; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; /* IPv4-only */ hints.ai_socktype = SOCK_STREAM; /* I want to have one address once and ONLY once, that's why I specify socktype and protocol */ hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_ADDRCONFIG; /* I don't need IPv4 addrs without IPv4 connectivity */ err = getaddrinfo(token, NULL, &hints, &ainfo); if (err == 0) { int count, taken; struct addrinfo *iter; struct sockaddr_in *resolved_addr; for (iter = ainfo, count = 0; iter; iter = iter->ai_next, ++count) ; taken = red_randui32() % count; for (iter = ainfo; taken > 0; iter = iter->ai_next, --taken) ; resolved_addr = (struct sockaddr_in*)iter->ai_addr; assert(resolved_addr->sin_family == iter->ai_family && iter->ai_family == AF_INET); if (count != 1) log_error(LOG_WARNING, "%s resolves to %d addresses, using %s", token, count, inet_ntoa(resolved_addr->sin_addr)); memcpy(addr, &resolved_addr->sin_addr, sizeof(ia)); freeaddrinfo(ainfo); } else { if (err == EAI_SYSTEM) parser_error(context, "unable to resolve %s, error %d (%s)", token, errno, strerror(errno)); else parser_error(context, "unable to resolve %s, getaddrinfo error %d (%s)", token, err, gai_strerror(err)); return -1; } } return 0; } static int vp_in_addr2(parser_context *context, void *addr, const char *token) { char *host = NULL, *mask = NULL; struct in_addr ia; int retval = 0; host = strdup(token); if (!host) { parser_error(context, "strdup failed"); return -1; } mask = strchr(host, '/'); if (mask) { *mask = '\0'; mask++; } if (inet_aton(host, &ia)) { memcpy(addr, &ia, sizeof(ia)); } else { parser_error(context, "invalid IP address"); retval = -1; } if (mask) { struct in_addr *pinmask = ((struct in_addr*)addr) + 1; char *end; unsigned long int uli = strtoul(mask, &end, 0);; if (*end == '.') { if (inet_aton(mask, &ia)) { memcpy(pinmask , &ia, sizeof(ia)); } else { parser_error(context, "invalid IP address"); retval = -1; } } else if (0 < uli && uli < 32) { pinmask->s_addr = htonl((INADDR_BROADCAST << (32 - uli))); } else { parser_error(context, "number of netmask bits out of range"); retval = -1; } } free(host); return retval; } static int vp_obsolete(parser_context *context, void *addr, const char *token) { parser_error(context, "obsolete key, delete it"); return -1; } static int vp_redsocks_max_accept_backoff(parser_context *context, void *addr, const char *token) { parser_error(context, "max_accept_backoff is not per-port setting anymore, move it from `redsocks` to `base`"); return -1; } static value_parser value_parser_by_type[] = { [pt_bool] = vp_pbool, [pt_pchar] = vp_pchar, [pt_uint16] = vp_uint16, [pt_uint32] = vp_uint32, [pt_in_addr] = vp_in_addr, [pt_in_addr2] = vp_in_addr2, [pt_disclose_src] = vp_disclose_src, [pt_on_proxy_fail] = vp_on_proxy_fail, [pt_obsolete] = vp_obsolete, [pt_redsocks_max_accept_backoff] = vp_redsocks_max_accept_backoff, }; int parser_run(parser_context *context) { char *section_token = NULL, *key_token = NULL, *value_token = NULL; parser_section *section = NULL; bool in_comment = false; bool need_more_space = false; bool need_more_data = true; while ( !context->error && !feof(context->fd) ) { assert(context->buffer.filled < context->buffer.size); // ``<`` and not ``<=`` if (need_more_space) { char *new = realloc(context->buffer.data, context->buffer.size * 2); if (!new) { parser_error(context, "realloc failure"); return -1; } context->buffer.data = new; context->buffer.size *= 2; need_more_space = false; } if (need_more_data) { // read one line per call char *sbegin = context->buffer.data + context->buffer.filled; int len; if (fgets(sbegin, context->buffer.size - context->buffer.filled, context->fd) == NULL) { if (ferror(context->fd)) { parser_error(context, "file read failure"); return -1; } else continue; } len = strlen(sbegin); context_filled_add(context, +len); if (len > 0 && sbegin[len - 1] == '\n') { context->line++; need_more_data = false; } else { need_more_space = true; continue; } } if ( in_comment ) { char *endc = strstr(context->buffer.data, "*/"); if (endc) { endc += 2; int comment_len = endc - context->buffer.data; memmove(context->buffer.data, endc, context->buffer.filled - comment_len); context_filled_add(context, -comment_len); in_comment = false; } else { context_filled_set(context, 0); need_more_data = true; continue; } } char *token = NULL, *iter = context->buffer.data; while ( !context->error && !in_comment && (token = gettoken(context, &iter)) ) { if (strcmp(token, "//") == 0) { char *endc = strchr(iter, '\n'); iter -= 2; endc += 1; // |*data |*iter |*endc -->|<--.filled int moved_len = context->buffer.filled - (endc - context->buffer.data); memmove(iter, endc, moved_len); context_filled_add(context, -(endc - iter)); } else if (strcmp(token, "/*") == 0) { int moved_len = iter - context->buffer.data; memmove(context->buffer.data, iter, context->buffer.filled - moved_len); context_filled_add(context, -moved_len); iter = context->buffer.data; in_comment = true; } else if (strcmp(token, "{") == 0) { // } - I love folding if (section) { parser_error(context, "section-in-section is invalid"); } else if (!section_token) { parser_error(context, "expected token before ``{''"); // } - I love folding } else { for (parser_section *p = context->sections; p; p = p->next) { if (strcmp(p->name, section_token) == 0) { section = p; break; } } if (section) { if (section->onenter) if ( section->onenter(section) == -1 ) parser_error(context, "section->onenter failed"); } else { parser_error(context, "unknown section <%s>", section_token); } FREE(section_token); } } else if (strcmp(token, "}") == 0) { // { - I love folding if (section) { if (section->onexit) if ( section->onexit(section) == -1 ) parser_error(context, "section->onexit failed"); section = NULL; } else { parser_error(context, "can't close non-opened section"); } } else if (strcmp(token, "=") == 0) { if (!section) { parser_error(context, "assignment used outside of any section"); } else if (!key_token) { parser_error(context, "assignment used without key"); } else { ; // What can I do? :) } } else if (strcmp(token, ";") == 0) { if (!section) { parser_error(context, "assignment termination outside of any section"); } else if (key_token && !value_token) { parser_error(context, "assignment has only key <%s> but no value", key_token); } else if (key_token && value_token) { parser_entry *e; for (e = section->entries; e->key; e++) if (strcmp(e->key, key_token) == 0) break; if (e->key) { if ( (value_parser_by_type[e->type])(context, e->addr, value_token) == -1 ) parser_error(context, "value <%s> can't be parsed for key <%s>", value_token, key_token); } else { parser_error(context, "assignment with unknown key <%s>", key_token); } } else { assert(false); } FREE(key_token); FREE(value_token); } else { if (!section && !section_token) { section_token = token; token = 0; } else if (section && !key_token) { key_token = token; token = 0; } else if (section && key_token && !value_token) { value_token = token; token = 0; } else { parser_error(context, "invalid token order: key_token=%s, value_token=%s, next token=%s", key_token, value_token, token); } } free(token); } int moved_len = iter - context->buffer.data; memmove(context->buffer.data, iter, context->buffer.filled - moved_len); context_filled_add(context, -moved_len); if (!token) need_more_data = true; } // file-error goes is thrown here if (section) parser_error(context, "unclosed section"); if (section_token) { parser_error(context, "stale section_token"); free(section_token); } if (key_token) { parser_error(context, "stale key_token"); free(key_token); } if (value_token) { parser_error(context, "stale value_token"); free(value_token); } return context->error ? -1 : 0; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/redsocks.conf.example0000644000175000017500000001172713030520742020627 0ustar apoikosapoikosbase { // debug: connection progress log_debug = off; // info: start and end of client session log_info = on; /* possible `log' values are: * stderr * "file:/path/to/file" * syslog:FACILITY facility is any of "daemon", "local0"..."local7" */ log = stderr; // log = "file:/path/to/file"; // log = "syslog:local7"; // detach from console daemon = off; /* Change uid, gid and root directory, these options require root * privilegies on startup. * Note, your chroot may requre /etc/localtime if you write log to syslog. * Log is opened before chroot & uid changing. * Debian, Ubuntu and some other distributions use `nogroup` instead of * `nobody`, so change it according to your system if you want redsocks * to drop root privileges. */ // user = nobody; // group = nobody; // chroot = "/var/chroot"; /* possible `redirector' values are: * iptables - for Linux * ipf - for FreeBSD * pf - for OpenBSD * generic - some generic redirector that MAY work */ redirector = iptables; /* Override per-socket values for TCP_KEEPIDLE, TCP_KEEPCNT, * and TCP_KEEPINTVL. see man 7 tcp for details. * `redsocks' relies on SO_KEEPALIVE option heavily. */ //tcp_keepalive_time = 0; //tcp_keepalive_probes = 0; //tcp_keepalive_intvl = 0; // Every `redsocks` connection needs two file descriptors for sockets. // If `splice` is enabled, it also needs four file descriptors for // pipes. `redudp` is not accounted at the moment. When max number of // connection is reached, redsocks tries to close idle connections. If // there are no idle connections, it stops accept()'ing new // connections, although kernel continues to fill listenq. // Set maximum number of open file descriptors (also known as `ulimit -n`). // 0 -- do not modify startup limit (default) // rlimit_nofile = 0; // Set maximum number of served connections. Default is to deduce safe // limit from `splice` setting and RLIMIT_NOFILE. // redsocks_conn_max = 0; // Close connections idle for N seconds when/if connection count // limit is hit. // 0 -- do not close idle connections // 7440 -- 2 hours 4 minutes, see RFC 5382 (default) // connpres_idle_timeout = 7440; // `max_accept_backoff` is a delay in milliseconds to retry `accept()` // after failure (e.g. due to lack of file descriptors). It's just a // safety net for misconfigured `redsocks_conn_max`, you should tune // redsocks_conn_max if accept backoff happens. // max_accept_backoff = 60000; } redsocks { /* `local_ip' defaults to 127.0.0.1 for security reasons, * use 0.0.0.0 if you want to listen on every interface. * `local_*' are used as port to redirect to. */ local_ip = 127.0.0.1; local_port = 12345; // listen() queue length. Default value is SOMAXCONN and it should be // good enough for most of us. // listenq = 128; // SOMAXCONN equals 128 on my Linux box. // Enable or disable faster data pump based on splice(2) syscall. // Default value depends on your kernel version, true for 2.6.27.13+ // splice = false; // `ip' and `port' are IP and tcp-port of proxy-server // You can also use hostname instead of IP, only one (random) // address of multihomed host will be used. ip = example.org; port = 1080; // known types: socks4, socks5, http-connect, http-relay type = socks5; // login = "foobar"; // password = "baz"; // known ways to disclose client IP to the proxy: // false -- disclose nothing // http-connect supports: // X-Forwarded-For -- X-Forwarded-For: IP // Forwarded_ip -- Forwarded: for=IP # see RFC7239 // Forwarded_ipport -- Forwarded: for="IP:port" # see RFC7239 // disclose_src = false; // various ways to handle proxy failure // close -- just close connection (default) // forward_http_err -- forward HTTP error page from proxy as-is // on_proxy_fail = close; } redudp { // `local_ip' should not be 0.0.0.0 as it's also used for outgoing // packets that are sent as replies - and it should be fixed // if we want NAT to work properly. local_ip = 127.0.0.1; local_port = 10053; // `ip' and `port' of socks5 proxy server. ip = 10.0.0.1; port = 1080; login = username; password = pazzw0rd; // redsocks knows about two options while redirecting UDP packets at // linux: TPROXY and REDIRECT. TPROXY requires more complex routing // configuration and fresh kernel (>= 2.6.37 according to squid // developers[1]) but has hack-free way to get original destination // address, REDIRECT is easier to configure, but requires `dest_ip` and // `dest_port` to be set, limiting packet redirection to single // destination. // [1] http://wiki.squid-cache.org/Features/Tproxy4 dest_ip = 8.8.8.8; dest_port = 53; udp_timeout = 30; udp_timeout_stream = 180; } dnstc { // fake and really dumb DNS server that returns "truncated answer" to // every query via UDP, RFC-compliant resolver should repeat same query // via TCP in this case. local_ip = 127.0.0.1; local_port = 5300; } // you can add more `redsocks' and `redudp' sections if you need. redsocks-release-0.5/md5.c0000644000175000017500000002737713030520742015352 0ustar apoikosapoikos/* Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.c is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order either statically or dynamically; added missing #include in library. 2002-03-11 lpd Corrected argument list for main(), and added int return type, in test program and T value program. 2002-02-21 lpd Added missing #include in test program. 2000-07-03 lpd Patched to eliminate warnings about "constant is unsigned in ANSI C, signed in traditional"; made test program self-checking. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 1999-05-03 lpd Original version. */ #include "md5.h" #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else # define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t)~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) #define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) #define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) #define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) #define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) #define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) #define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) #define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) #define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) #define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) #define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) #define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) #define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) #define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) #define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) #define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) #define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) #define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) #define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) #define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) #define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; const md5_word_t *X; #endif { #if BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static const int w = 1; if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ #endif #if BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (const md5_byte_t *)0) & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { /* not aligned */ memcpy(xbuf, data, 64); X = xbuf; } } #endif #if BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; # if BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ # else # define xbuf X /* (static only) */ # endif for (i = 0; i < 16; ++i, xp += 4) xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } #endif } #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + F(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, T1); SET(d, a, b, c, 1, 12, T2); SET(c, d, a, b, 2, 17, T3); SET(b, c, d, a, 3, 22, T4); SET(a, b, c, d, 4, 7, T5); SET(d, a, b, c, 5, 12, T6); SET(c, d, a, b, 6, 17, T7); SET(b, c, d, a, 7, 22, T8); SET(a, b, c, d, 8, 7, T9); SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + G(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, T17); SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); SET(b, c, d, a, 0, 20, T20); SET(a, b, c, d, 5, 5, T21); SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); SET(b, c, d, a, 4, 20, T24); SET(a, b, c, d, 9, 5, T25); SET(d, a, b, c, 14, 9, T26); SET(c, d, a, b, 3, 14, T27); SET(b, c, d, a, 8, 20, T28); SET(a, b, c, d, 13, 5, T29); SET(d, a, b, c, 2, 9, T30); SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti)\ t = a + H(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, T33); SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); SET(a, b, c, d, 1, 4, T37); SET(d, a, b, c, 4, 11, T38); SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); SET(a, b, c, d, 13, 4, T41); SET(d, a, b, c, 0, 11, T42); SET(c, d, a, b, 3, 16, T43); SET(b, c, d, a, 6, 23, T44); SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); SET(b, c, d, a, 2, 23, T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti)\ t = a + I(b,c,d) + X[k] + Ti;\ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, T49); SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); SET(b, c, d, a, 5, 21, T52); SET(a, b, c, d, 12, 6, T53); SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); SET(b, c, d, a, 1, 21, T56); SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); SET(c, d, a, b, 2, 15, T63); SET(b, c, d, a, 9, 21, T64); #undef SET /* Then perform the following additions. (That is increment each of the four registers by the value it had before this block was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) { const md5_byte_t *p = data; int left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) pms->count[1]++; /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) md5_process(pms, p); /* Process a final partial block. */ if (left) memcpy(pms->buf, p, left); } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } redsocks-release-0.5/utils.c0000644000175000017500000001432413030520742016011 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "log.h" #include "base.h" #include "utils.h" #include "redsocks.h" // for redsocks_close #include "libc-compat.h" int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr) { socklen_t addrlen = sizeof(*inaddr); ssize_t pktlen; struct msghdr msg; struct iovec io; char control[1024]; memset(&msg, 0, sizeof(msg)); msg.msg_name = inaddr; msg.msg_namelen = sizeof(*inaddr); msg.msg_iov = &io; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); io.iov_base = buf; io.iov_len = buflen; pktlen = recvmsg(fd, &msg, 0); if (pktlen == -1) { log_errno(LOG_WARNING, "recvfrom"); return -1; } if (toaddr) { memset(toaddr, 0, sizeof(*toaddr)); for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if ( cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_ORIGDSTADDR && cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr)) ) { struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg); char buf[RED_INET_ADDRSTRLEN]; log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf))); memcpy(toaddr, cmsgaddr, sizeof(*toaddr)); } else { log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)", cmsg->cmsg_level, cmsg->cmsg_type); } } if (toaddr->sin_family != AF_INET) { log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found"); return -1; } } if (addrlen != sizeof(*inaddr)) { log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); return -1; } if (pktlen >= buflen) { char buf[RED_INET_ADDRSTRLEN]; log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...", pktlen, red_inet_ntop(inaddr, buf, sizeof(buf))); return -1; } return pktlen; } uint32_t red_randui32() { uint32_t ret; evutil_secure_rng_get_bytes(&ret, sizeof(ret)); return ret; } time_t redsocks_time(time_t *t) { time_t retval; retval = time(t); if (retval == ((time_t) -1)) log_errno(LOG_WARNING, "time"); return retval; } int redsocks_gettimeofday(struct timeval *tv) { int retval = gettimeofday(tv, NULL); if (retval != 0) log_errno(LOG_WARNING, "gettimeofday"); return retval; } char *redsocks_evbuffer_readline(struct evbuffer *buf) { #if _EVENT_NUMERIC_VERSION >= 0x02000000 return evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); #else return evbuffer_readline(buf); #endif } struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg) { struct bufferevent *retval = NULL; int relay_fd = -1; int error; relay_fd = socket(AF_INET, SOCK_STREAM, 0); if (relay_fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = fcntl_nonblock(relay_fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } if (apply_tcp_keepalive(relay_fd)) goto fail; error = connect(relay_fd, (struct sockaddr*)addr, sizeof(*addr)); if (error && errno != EINPROGRESS) { log_errno(LOG_NOTICE, "connect"); goto fail; } retval = bufferevent_new(relay_fd, NULL, writecb, errorcb, cbarg); if (!retval) { log_errno(LOG_ERR, "bufferevent_new"); goto fail; } relay_fd = -1; error = bufferevent_enable(retval, EV_WRITE); // we wait for connection... if (error) { log_errno(LOG_ERR, "bufferevent_enable"); goto fail; } return retval; fail: if (relay_fd != -1) redsocks_close(relay_fd); if (retval) redsocks_bufferevent_free(retval); return NULL; } int red_socket_geterrno(struct bufferevent *buffev) { int error; int pseudo_errno; socklen_t optlen = sizeof(pseudo_errno); assert(event_get_fd(&buffev->ev_read) == event_get_fd(&buffev->ev_write)); error = getsockopt(event_get_fd(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); if (error) { log_errno(LOG_ERR, "getsockopt"); return -1; } return pseudo_errno; } /** simple fcntl(2) wrapper, provides errno and all logging to caller * I have to use it in event-driven code because of accept(2) (see NOTES) * and connect(2) (see ERRORS about EINPROGRESS) */ int fcntl_nonblock(int fd) { int error; int flags; flags = fcntl(fd, F_GETFL); if (flags == -1) return -1; error = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (error) return -1; return 0; } int red_is_socket_connected_ok(struct bufferevent *buffev) { int pseudo_errno = red_socket_geterrno(buffev); if (pseudo_errno == -1) { return 0; } else if (pseudo_errno) { errno = pseudo_errno; log_errno(LOG_NOTICE, "connect"); return 0; } else { return 1; } } char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size) { const char *retval = 0; size_t len = 0; uint16_t port; const char placeholder[] = "???:???"; assert(buffer_size >= RED_INET_ADDRSTRLEN); memset(buffer, 0, buffer_size); if (sa->sin_family == AF_INET) { retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size); port = ((struct sockaddr_in*)sa)->sin_port; } else if (sa->sin_family == AF_INET6) { retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer, buffer_size); port = ((struct sockaddr_in6*)sa)->sin6_port; } if (retval) { assert(retval == buffer); len = strlen(retval); snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port)); } else { strcpy(buffer, placeholder); } return buffer; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ redsocks-release-0.5/dnstc.h0000644000175000017500000000056713030520742015775 0ustar apoikosapoikos#ifndef DNSTC_H #define DNSTC_H typedef struct dnstc_config_t { struct sockaddr_in bindaddr; } dnstc_config; typedef struct dnstc_instance_t { list_head list; dnstc_config config; struct event listener; } dnstc_instance; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDUDP_H */ redsocks-release-0.5/tests/0000755000175000017500000000000013030520742015643 5ustar apoikosapoikosredsocks-release-0.5/tests/run0000755000175000017500000000004013030520742016367 0ustar apoikosapoikos#!/bin/sh exec py.test -v "$@" redsocks-release-0.5/tests/regw/0000755000175000017500000000000013030520742016607 5ustar apoikosapoikosredsocks-release-0.5/tests/regw/Dockerfile0000644000175000017500000000063513030520742020605 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y libevent-2.0-5 valgrind curl strace COPY redsocks /usr/local/sbin/ COPY redsocks.conf /usr/local/etc/ CMD ["/usr/local/sbin/redsocks", "-c", "/usr/local/etc/redsocks.conf"] redsocks-release-0.5/tests/regw/redsocks.conf0000644000175000017500000000557213030520742021304 0ustar apoikosapoikosbase { log_debug = on; log_info = on; log = stderr; daemon = off; redirector = iptables; redsocks_conn_max = 8; } redsocks { local_ip = 0.0.0.0; local_port = 12340; ip = 10.0.1.8; port = 8080; type = http-connect; } redsocks { local_ip = 0.0.0.0; local_port = 12341; ip = 10.0.1.8; port = 8081; type = http-connect; login = basic_user; password = basic_password; } redsocks { local_ip = 0.0.0.0; local_port = 12342; ip = 10.0.1.9; port = 8081; type = http-connect; login = digest_user; password = digest_password; } redsocks { local_ip = 0.0.0.0; local_port = 12343; ip = 10.0.1.180; port = 1080; type = socks5; } redsocks { local_ip = 0.0.0.0; local_port = 12344; ip = 10.0.1.181; port = 1081; type = socks5; login = sockusr; password = sockpwd; } redsocks { local_ip = 0.0.0.0; local_port = 12345; ip = 10.0.1.9; port = 8081; type = http-connect; } redsocks { local_ip = 0.0.0.0; local_port = 12346; ip = 10.0.1.9; port = 8081; type = http-connect; login = luser; password = digest_password; } redsocks { local_ip = 0.0.0.0; local_port = 12347; ip = 10.0.1.9; port = 8081; type = http-connect; login = digest_user; password = buzzword; } redsocks { local_ip = 0.0.0.0; local_port = 12348; ip = 10.0.1.181; port = 1081; type = socks5; } redsocks { local_ip = 0.0.0.0; local_port = 12349; ip = 10.0.1.181; port = 1081; type = socks5; login = luser; password = sockpwd; } redsocks { local_ip = 0.0.0.0; local_port = 12350; ip = 10.0.1.181; port = 1081; type = socks5; login = sockusr; password = buzzword; } redsocks { local_ip = 0.0.0.0; local_port = 12351; ip = 10.0.1.9; port = 8081; type = http-connect; on_proxy_fail = forward_http_err; } redsocks { local_ip = 0.0.0.0; local_port = 12352; ip = 10.0.1.9; port = 8081; type = http-connect; login = luser; password = digest_password; on_proxy_fail = forward_http_err; } redsocks { local_ip = 0.0.0.0; local_port = 12353; ip = 10.0.1.9; port = 8081; type = http-connect; login = digest_user; password = buzzword; on_proxy_fail = forward_http_err; } redsocks { local_ip = 0.0.0.0; local_port = 12354; ip = 10.0.1.9; port = 8081; type = http-connect; login = digest_user; password = digest_password; on_proxy_fail = forward_http_err; } redsocks { local_ip = 0.0.0.0; local_port = 12355; ip = 10.0.1.9; port = 1; type = http-connect; on_proxy_fail = forward_http_err; } redsocks { local_ip = 0.0.0.0; local_port = 12356; ip = 10.0.1.66; port = 66; type = http-connect; on_proxy_fail = forward_http_err; } redsocks-release-0.5/tests/conftest.py0000644000175000017500000002102513030520742020042 0ustar apoikosapoikosfrom functools import partial from multiprocessing.dummy import Pool as ThrPool from subprocess import check_call, check_output, STDOUT import multiprocessing import os import time import pytest # 10.0.1.0/24 (br-web) -- web, squid, inetd, gw # 10.0.2.0/24 (br-tank) -- tank, openwrt # 10.0.8.0/24 (br-txrx) -- gw, openwrt BR = { '10.0.1': 'web', '10.0.8': 'txrx', '10.0.2': 'tank', } GW = { 'web': '10.0.1.1', 'txrx': '10.0.8.1', 'tank': '10.0.2.123', } SLEEPING_BEAST = 'sleep 3600' DOCKER_CMD = os.environ.get('DOCKER_CMD', '/usr/bin/docker') PIPEWORK_CMD = os.environ.get('PIPEWORK_CMD', '/usr/bin/pipework') class VM(object): def __init__(self, name, tag, ip4=None, cmd='', docker_opt=''): self.docker = DOCKER_CMD self.pipework = PIPEWORK_CMD self.netns = '/var/run/netns' self.dns = '8.8.8.8' self.name, self.tag = name, tag self.cmd, self.docker_opt = cmd, docker_opt if ip4: self.ip4 = ip4 self.sha = self.output('sudo docker run --detach --dns {dns} --name {name} --hostname {name} {docker_opt} {tag} {cmd}') self.pid = int(self.output('docker inspect -f {{{{.State.Pid}}}} {sha}')) if not os.path.exists(self.netns): self.call('sudo mkdir {netns}') self.call('sudo ln -sf /proc/{pid}/ns/net {netns}/{name}') self.net() while cmd != SLEEPING_BEAST and 'LISTEN' not in self.do('netstat -ltn'): time.sleep(0.1) def net(self): self.net_noext() self.net_br() def net_br(self): self.net_br_gw() def net_noext(self): self.netcall('ip link set dev eth0 down') self.netcall('ip route replace unreachable {dns}/32') def net_br_gw(self): self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24@{gw4}') def net_br_nogw(self): self.call('sudo {pipework} br-{br} -i {intif} -l {vethif} {name} {ip4}/24') @property def br(self): return BR[self.ip4.rsplit('.', 1)[0]] @property def gw4(self): return GW[self.br] @property def intif(self): return {'web': 'ethw', 'tank': 'etht', 'txrx': 'ethx'}[self.br] @property def vethif(self): return ('v' + self.intif + self.name)[:15] # IFNAMSIZ 16 def assert_good_death(self): pass def close(self): if hasattr(self, 'sha'): self.call('sudo docker stop --time 1 {sha}') self.assert_good_death() if not getattr(self, 'preserve_root', False): self.call('sudo docker rm {sha}') del self.sha def fmt(self, cmd): ctx = self.__dict__.copy() for i in xrange(len(dir(self))): try: ret = cmd.format(**ctx).split() break except KeyError, e: key = e.args[0] ctx[key] = getattr(self, key) return ret def output(self, cmd, **kwargs): return check_output(self.fmt(cmd), **kwargs) def logs(self, since=None): out = self.output('sudo docker logs {sha}', stderr=STDOUT) out = [_.split(None, 1) for _ in out.split('\n')] out = [(float(_[0]), _[1]) for _ in out if len(_) == 2 and _[0][:10].isdigit()] if since is not None: out = [_ for _ in out if _[0] >= since] return out def call(self, cmd): check_call(self.fmt(cmd)) def do(self, cmd): return self.output('sudo docker exec {sha} ' + cmd) def netcall(self, cmd): return self.output('sudo ip netns exec {name} ' + cmd) def lsfd(self): out = self.output('sudo lsof -p {pid} -F f') out = [int(_[1:]) for _ in out.split() if _[0] == 'f' and _[1:].isdigit()] assert len(out) > 0 return out class WebVM(VM): def __init__(self): VM.__init__(self, 'web', 'redsocks/web', '10.0.1.80') class InetdVM(VM): def __init__(self): VM.__init__(self, 'inetd', 'redsocks/inetd', '10.0.1.13') class SquidVM(VM): def __init__(self, no): VM.__init__(self, 'squid-%d' % no, 'redsocks/squid', '10.0.1.%d' % no, docker_opt='--ulimit nofile=65535:65535', cmd='/etc/squid3/squid-%d.conf' % no) def net(self): self.net_br_nogw() self.netcall('ip route replace 10.0.0.0/16 via 10.0.1.1') class DanteVM(VM): def __init__(self, no): VM.__init__(self, 'dante-%d' % no, 'redsocks/dante', '10.0.1.%d' % (180 + no), cmd='/etc/danted-%d.conf' % (1080 + no)) def net(self): self.net_br_nogw() self.netcall('ip route replace 10.0.0.0/16 via 10.0.1.1') class GwVM(VM): def __init__(self): VM.__init__(self, 'gw', 'ubuntu:14.04', cmd=SLEEPING_BEAST) def net_br(self): self.ip4 = '10.0.1.1' self.net_br_nogw() self.ip4 = '10.0.8.1' self.net_br_nogw() del self.ip4 self.netcall('ip route replace unreachable 10.0.2.0/24') self.netcall('iptables -A FORWARD --destination 10.0.1.66/32 -j DROP') class TankVM(VM): def __init__(self, no): assert 1 <= no <= 100 VM.__init__(self, 'tank%d' % no, 'redsocks/tank', '10.0.2.%d' % no, cmd=SLEEPING_BEAST) class RegwVM(VM): def __init__(self): kw = {} self.debug = os.environ.get('DEBUG_TEST', '') if self.debug: self.preserve_root = True if self.debug != 'logs': kw['cmd'] = { 'valgrind': 'valgrind --leak-check=full --show-leak-kinds=all /usr/local/sbin/redsocks -c /usr/local/etc/redsocks.conf', 'strace': 'strace -ttt /usr/local/sbin/redsocks -c /usr/local/etc/redsocks.conf', }[self.debug] VM.__init__(self, 'regw', 'redsocks/regw', **kw) def net_br(self): self.ip4 = '10.0.2.123' self.net_br_nogw() self.ip4 = '10.0.8.123' self.net_br_gw() del self.ip4 for t in TANKS.values(): self.netcall('iptables -t nat -A PREROUTING --source 10.0.2.%d/32 --dest 10.0.1.0/24 -p tcp -j REDIRECT --to-port %d' % (t, 12340 + t - TANKS_BASE)) def assert_good_death(self): out = self.output('sudo docker logs {sha}', stderr=STDOUT) assert 'redsocks goes down' in out and 'There are connected clients during shutdown' not in out assert self.debug != 'valgrind' or 'no leaks are possible' in out CPU = object() MAX = object() def pmap(l, j=MAX): #return map(lambda x: x(), l) if j is MAX: j = len(l) elif j is CPU: j = multiprocessing.cpu_count() p = ThrPool(j) try: return p.map(lambda x: x(), l, chunksize=1) finally: p.close() p.join() TANKS_BASE = 10 TANKS = { 'connect_none': TANKS_BASE + 0, 'connect_basic': TANKS_BASE + 1, 'connect_digest': TANKS_BASE + 2, 'socks5_none': TANKS_BASE + 3, 'socks5_auth': TANKS_BASE + 4, 'connect_nopass': TANKS_BASE + 5, 'connect_baduser': TANKS_BASE + 6, 'connect_badpass': TANKS_BASE + 7, 'socks5_nopass': TANKS_BASE + 8, 'socks5_baduser': TANKS_BASE + 9, 'socks5_badpass': TANKS_BASE + 10, 'httperr_connect_nopass': TANKS_BASE + 11, 'httperr_connect_baduser': TANKS_BASE + 12, 'httperr_connect_badpass': TANKS_BASE + 13, 'httperr_connect_digest': TANKS_BASE + 14, 'httperr_proxy_refuse': TANKS_BASE + 15, 'httperr_proxy_timeout': TANKS_BASE + 16, } class _Network(object): def __init__(self): assert os.path.exists(PIPEWORK_CMD) running = check_output('sudo docker ps -q'.split()).split() assert running == [] names = check_output('sudo docker ps -a --format {{.Names}}'.split()).split() assert 'regw' not in names vm = [ GwVM, WebVM, InetdVM, RegwVM, partial(SquidVM, 8), partial(SquidVM, 9), partial(DanteVM, 0), partial(DanteVM, 1), ] for t in TANKS.values(): vm.append(partial(TankVM, t)) self.vm = {_.name: _ for _ in pmap(vm, j=CPU)} # pmap saves ~5 seconds def close(self): check_output('sudo docker ps'.split()) pmap([_.close for _ in self.vm.values()]) # pmap saves ~7 seconds @pytest.fixture(scope="session") def net(request): n = _Network() request.addfinalizer(n.close) return n def pytest_addoption(parser): parser.addoption('--vmdebug', action='store_true', help='run `test_debug` test') def pytest_cmdline_preparse(args): if '--vmdebug' in args: args[:] = ['-k', 'test_vmdebug'] + args redsocks-release-0.5/tests/web/0000755000175000017500000000000013030520742016420 5ustar apoikosapoikosredsocks-release-0.5/tests/web/Dockerfile0000644000175000017500000000104213030520742020407 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y nginx-light RUN set -o xtrace \ && dd if=/dev/urandom of=/usr/share/nginx/html/128K count=1 bs=128K \ && dd if=/dev/urandom of=/usr/share/nginx/html/1M count=1 bs=1M \ && for i in `seq 16`; do cat /usr/share/nginx/html/1M; done >/usr/share/nginx/html/16M CMD ["/usr/sbin/nginx", "-g", "daemon off;"] redsocks-release-0.5/tests/squid/0000755000175000017500000000000013030520742016770 5ustar apoikosapoikosredsocks-release-0.5/tests/squid/digest.passwd0000644000175000017500000000003413030520742021467 0ustar apoikosapoikosdigest_user:digest_password redsocks-release-0.5/tests/squid/squid-9.conf0000644000175000017500000000264613030520742021142 0ustar apoikosapoikosauth_param digest program /usr/lib/squid3/digest_file_auth /etc/squid3/digest.passwd auth_param digest children 5 startup=0 idle=1 auth_param digest realm Squid proxy-caching web server auth_param digest nonce_garbage_interval 5 minutes auth_param digest nonce_max_duration 30 minutes auth_param digest nonce_max_count 50 acl good proxy_auth digest_user # same conf after the line acl portopen localport 8080 acl portauth localport 8081 acl regw src 10.0.8.123/32 http_access deny manager http_access deny to_localhost http_access allow regw portopen http_access allow regw portauth good http_access deny all http_port 0.0.0.0:8080 http_port 0.0.0.0:8081 #http_port 0.0.0.0:8082 #ssl_bump splice all # http_port [::]:8080 #http_port 127.0.0.1:12345 intercept #http_port [::1]:12345 intercept #https_port 127.0.0.1:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key #https_port [::1]:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key #cache_peer 80.80.80.80 parent 8080 0 no-digest no-netdb-exchange proxy-only #cache_peer_access 80.80.80.80 allow all #never_direct allow all forwarded_for delete via off reply_header_access X-Cache-Lookup deny all reply_header_access X-Squid-Error deny all reply_header_access X-Cache deny all request_header_access Cache-Control deny all cache deny all memory_pools off redsocks-release-0.5/tests/squid/basic.passwd0000644000175000017500000000006113030520742021271 0ustar apoikosapoikosbasic_user:$apr1$bslox4us$3RXJauJbX8riqzVzzOYc70 redsocks-release-0.5/tests/squid/Dockerfile0000644000175000017500000000063613030520742020767 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y squid3 COPY squid-?.conf basic.passwd digest.passwd /etc/squid3/ # that's from /etc/init/squid3.conf ENTRYPOINT ["/usr/sbin/squid3", "-NYC", "-f"] CMD ["/etc/squid3/squid-8.conf"] redsocks-release-0.5/tests/squid/squid-8.conf0000644000175000017500000000250013030520742021126 0ustar apoikosapoikosauth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid3/basic.passwd auth_param basic children 5 startup=0 idle=1 auth_param basic realm Squid proxy-caching web server auth_param basic credentialsttl 2 hours acl good proxy_auth basic_user # same conf after the line acl portopen localport 8080 acl portauth localport 8081 acl regw src 10.0.8.123/32 http_access deny manager http_access deny to_localhost http_access allow regw portopen http_access allow regw portauth good http_access deny all http_port 0.0.0.0:8080 http_port 0.0.0.0:8081 #http_port 0.0.0.0:8082 #ssl_bump splice all # http_port [::]:8080 #http_port 127.0.0.1:12345 intercept #http_port [::1]:12345 intercept #https_port 127.0.0.1:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key #https_port [::1]:12346 intercept ssl-bump cert=/etc/ssl/certs/ssl-cert-snakeoil.pem key=/etc/ssl/private/ssl-cert-snakeoil.key #cache_peer 80.80.80.80 parent 8080 0 no-digest no-netdb-exchange proxy-only #cache_peer_access 80.80.80.80 allow all #never_direct allow all forwarded_for delete via off reply_header_access X-Cache-Lookup deny all reply_header_access X-Squid-Error deny all reply_header_access X-Cache deny all request_header_access Cache-Control deny all cache deny all memory_pools off redsocks-release-0.5/tests/inetd/0000755000175000017500000000000013030520742016746 5ustar apoikosapoikosredsocks-release-0.5/tests/inetd/Dockerfile0000644000175000017500000000063513030520742020744 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y xinetd RUN set -o xtrace \ && apt-get install -y iperf COPY testing /etc/xinetd.d/testing CMD ["/usr/sbin/xinetd", "-dontfork", "-pidfile", "/run/xinetd.pid", "-inetd_ipv6"] redsocks-release-0.5/tests/inetd/testing0000644000175000017500000000114513030520742020347 0ustar apoikosapoikosservice daytime { disable = no type = INTERNAL id = daytime-stream socket_type = stream protocol = tcp user = root wait = no flags = IPv6 } service discard { disable = no type = INTERNAL id = discard-stream socket_type = stream protocol = tcp user = root wait = no flags = IPv6 } service echo { disable = no type = INTERNAL id = echo-stream socket_type = stream protocol = tcp user = root wait = no flags = IPv6 } service chargen { disable = no type = INTERNAL id = chargen-stream socket_type = stream protocol = tcp user = root wait = no flags = IPv6 } redsocks-release-0.5/tests/dante/0000755000175000017500000000000013030520742016736 5ustar apoikosapoikosredsocks-release-0.5/tests/dante/danted-waitif0000755000175000017500000000016513030520742021406 0ustar apoikosapoikos#!/bin/sh -x while ! ip link show ethw up 2>/dev/null | grep -q UP; do sleep 0.1 done exec /usr/sbin/danted "$@" redsocks-release-0.5/tests/dante/Dockerfile0000644000175000017500000000114613030520742020732 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y dante-server RUN useradd sockusr --user-group \ --home-dir /nonexistent --no-create-home \ --shell /usr/sbin/nologin \ --password '$6$U1HPxoVq$XFqhRetreV3068UCwQA//fGVFUfwfyqeiYpCpeUFAhuMi/wjOhJSzUxb4wUqt9vRnWjO0CDPMkE40ptHWrrIz.' COPY danted-*.conf /etc/ COPY danted-waitif /usr/sbin/ ENTRYPOINT ["/usr/sbin/danted-waitif", "-d", "-f"] CMD ["/etc/danted-1080.conf"] redsocks-release-0.5/tests/dante/danted-1081.conf0000644000175000017500000000102313030520742021427 0ustar apoikosapoikoslogoutput: stderr internal: 0.0.0.0 port = 1081 external: ethw method: username clientmethod: none #extension: bind # TCP addr rules client pass { from: 10.0.8.123/32 port 1-65535 to: 0.0.0.0/0 log: connect error } client block { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error } # Socks protocol rules block { from: 0.0.0.0/0 to: 127.0.0.0/8 log: connect error } pass { from: 10.0.8.123/32 to: 0.0.0.0/0 log: connect error } block { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error } redsocks-release-0.5/tests/dante/danted-1080.conf0000644000175000017500000000101713030520742021431 0ustar apoikosapoikoslogoutput: stderr internal: 0.0.0.0 port = 1080 external: ethw method: none clientmethod: none #extension: bind # TCP addr rules client pass { from: 10.0.8.123/32 port 1-65535 to: 0.0.0.0/0 log: connect error } client block { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error } # Socks protocol rules block { from: 0.0.0.0/0 to: 127.0.0.0/8 log: connect error } pass { from: 10.0.8.123/32 to: 0.0.0.0/0 log: connect error } block { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect error } redsocks-release-0.5/tests/prlimit-nofile.c0000644000175000017500000000144113030520742020741 0ustar apoikosapoikos#include #include #include #include // prlimit(1) is util-linux 2.21+ and I'm stuck with 2.20 at the moment int main(int argc, char *argv[]) { if (argc != 3) errx(EXIT_FAILURE, "Usage: %s ", argv[0]); pid_t pid = atoi(argv[1]); rlim_t soft = atoi(argv[2]); struct rlimit rl; if (prlimit(pid, RLIMIT_NOFILE, NULL, &rl) == -1) err(EXIT_FAILURE, "prlimit(%d, RLIMIT_NOFILE, NULL, %p)", pid, &rl); if (rl.rlim_max < soft) errx(EXIT_FAILURE, "rlim_max = %ld, requested limit = %ld", rl.rlim_max, soft); rl.rlim_cur = soft; if (prlimit(pid, RLIMIT_NOFILE, &rl, NULL) == -1) err(EXIT_FAILURE, "prlimit(%d, RLIMIT_NOFILE, %p, NULL)", pid, &rl); return 0; } redsocks-release-0.5/tests/login0000755000175000017500000000027513030520742016705 0ustar apoikosapoikos#!/bin/sh vm=$1 shift if [ $# = 0 ]; then set -- /bin/bash fi if [ -t 0 -a -t 1 -a -t 2 ]; then tty="--tty" else tty="" fi set -x sudo docker exec --interactive $tty "$vm" "$@" redsocks-release-0.5/tests/tank/0000755000175000017500000000000013030520742016600 5ustar apoikosapoikosredsocks-release-0.5/tests/tank/Dockerfile0000644000175000017500000000116513030520742020575 0ustar apoikosapoikosFROM ubuntu:14.04 RUN set -o xtrace \ && sed -i 's,^deb-src,# no src # &,; s,http://archive.ubuntu.com/ubuntu/,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list \ && apt-get update \ && apt-get install -y apache2-utils curl pv python-tornado RUN set -o xtrace \ && apt-get install -y iperf COPY benchmark.py /benchmark.py # yandex-tank is nice `ab' replacement, but it's PPA is broken at the moment # https://github.com/yandex/yandex-tank/issues/202 # # apt-get install -y software-properties-common # add-apt-repository ppa:yandex-load/main # apt-get install -y phantom phantom-ssl yandex-tank redsocks-release-0.5/tests/tank/benchmark.py0000644000175000017500000000751613030520742021115 0ustar apoikosapoikos#!/usr/bin/python2 import argparse import functools import socket import logging import json import time import datetime import sys import tornado.ioloop as ioloop import tornado.iostream as iostream import tornado.httpclient as httpclient log = logging.getLogger('benchmark') conlist = [] stats = [] DELAY = 0.1 class Mode(object): def __init__(self, opt): self.opt = opt conlist.append(self) log.debug('%d of %d', len(conlist), opt.stop) self.start() def grab_stats(self): if self.opt.json is not None: client = httpclient.AsyncHTTPClient() self.begin_stats = time.time() client.fetch('http://%s/debug/meminfo.json' % self.opt.json, self.parse_stats) else: self.schedule_next() def parse_stats(self, response): log.debug('got stats') self.end_stats = time.time() sdict = json.loads(response.body) sdict['begin'] = self.begin_stats sdict['end'] = self.end_stats sdict['conns'] = len(conlist) stats.append(sdict) self.schedule_next() def schedule_next(self): if len(conlist) < self.opt.stop: ioloop.IOLoop.current().add_timeout(datetime.timedelta(seconds=DELAY), functools.partial(self.__class__, self.opt)) else: ioloop.IOLoop.current().stop() class Pipe(Mode): def start(self): client = httpclient.AsyncHTTPClient() self.begin_stats = time.time() client.fetch('http://%s/debug/pipe' % self.opt.json, self.on_pipe) def on_pipe(self, response): self.grab_stats() class Connector(Mode): def start(self): fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = iostream.IOStream(fd) self.stream.connect((opt.host, opt.port), self.on_connect) class EchoConnector(Connector): ECHO = 'ECHO\x0d\x0a' def on_connect(self): self.stream.write(self.ECHO) self.stream.read_bytes(len(self.ECHO), self.on_pong) def on_pong(self, data): assert data == self.ECHO self.grab_stats() class ChargenConnector(Connector): def __init__(self, opt): Connector.__init__(self, opt) self.gotstats = False def on_connect(self): self.stream.read_until_close(self.on_close, self.on_datachunk) def on_close(self, data): pass def on_datachunk(self, data): if not self.gotstats: self.gotstats = True self.grab_stats() MODES = { 'echo': EchoConnector, 'chargen': ChargenConnector, 'pipe': Pipe, } def parse_cmd(argv): parser = argparse.ArgumentParser(prog=argv[0], description='/ping handler') parser.add_argument('--json', type=str) parser.add_argument('--stop', default=10, type=int) parser.add_argument('--verbose', default=False, action='store_true') parser.add_argument('--mode', metavar='MODE', required=True, choices=sorted(MODES)) parser.add_argument('--port', metavar='PORT', type=int, default=None) parser.add_argument('host', metavar='HOST', type=str) if len(sys.argv) == 1: sys.argv.append('-h') opt = parser.parse_args(argv[1:]) if opt.port is None: opt.port = {'echo': 7, 'chargen': 19, 'pipe': -1}[opt.mode] return opt def main(opt): LOGGING_FORMAT = '%(asctime)s %(name)s[%(process)d] %(levelname)s: %(message)s' if not opt.verbose: logging.basicConfig(level=logging.INFO, format=LOGGING_FORMAT) logging.getLogger('tornado.access').setLevel(logging.INFO+1) else: logging.basicConfig(level=logging.DEBUG, format=LOGGING_FORMAT) klass = MODES[opt.mode] klass(opt) try: ioloop.IOLoop.instance().start() except KeyboardInterrupt: pass finally: log.fatal('Done.') print json.dumps(stats) if __name__ == '__main__': main(parse_cmd(sys.argv)) redsocks-release-0.5/tests/test_smoke.py0000644000175000017500000001750413030520742020401 0ustar apoikosapoikosfrom functools import partial from subprocess import check_call, CalledProcessError, Popen, PIPE import time import conftest import pytest @pytest.mark.skipif(not pytest.config.getoption('--vmdebug'), reason='need --vmdebug option to run') def test_vmdebug(net): check_call('sleep 365d'.split()) GOOD_AUTH = 'connect_none connect_basic connect_digest socks5_none socks5_auth httperr_connect_digest'.split() BAD_AUTH = 'connect_nopass connect_baduser connect_badpass socks5_nopass socks5_baduser socks5_badpass httperr_proxy_refuse'.split() UGLY_AUTH = 'httperr_connect_nopass httperr_connect_baduser httperr_connect_badpass'.split() assert set(conftest.TANKS) == set(GOOD_AUTH + BAD_AUTH + UGLY_AUTH + ['httperr_proxy_timeout']) @pytest.mark.parametrize('tank', GOOD_AUTH) def test_smoke(net, tank): vm = net.vm['tank%d' % conftest.TANKS[tank]] page = vm.do('curl --max-time 0.5 http://10.0.1.80/') assert 'Welcome to nginx!' in page @pytest.mark.parametrize('tank', BAD_AUTH) def test_badauth(net, tank): vm = net.vm['tank%d' % conftest.TANKS[tank]] with pytest.raises(CalledProcessError) as excinfo: vm.do('curl --max-time 0.5 http://10.0.1.80/') assert excinfo.value.returncode == 52 # Empty reply from server @pytest.mark.parametrize('tank', UGLY_AUTH) def test_uglyauth(net, tank): vm = net.vm['tank%d' % conftest.TANKS[tank]] page = vm.do('curl -sSv --max-time 0.5 http://10.0.1.80/') assert '' in page @pytest.mark.parametrize('tank', set(conftest.TANKS) - set(UGLY_AUTH + ['httperr_connect_digest'])) def test_econnrefused(net, tank): vm = net.vm['tank%d' % conftest.TANKS[tank]] with pytest.raises(CalledProcessError) as excinfo: vm.do('curl --max-time 0.5 http://10.0.1.80:81/') if tank == 'httperr_proxy_timeout': assert excinfo.value.returncode == 28 # Operation timed out else: assert excinfo.value.returncode == 52 # Empty reply from server def test_econnrefused_httperr(net): tank = 'httperr_connect_digest' vm = net.vm['tank%d' % conftest.TANKS[tank]] page = vm.do('curl --max-time 0.5 http://10.0.1.80:81/') assert '' in page RTT = 200 # ms @pytest.fixture(scope="function") def slow_net(request, net): def close(): net.vm['gw'].netcall('tc qdisc del dev ethx root') net.vm['gw'].netcall('tc qdisc del dev ethw root') request.addfinalizer(close) net.vm['gw'].netcall('tc qdisc add dev ethw root netem delay %dms' % (RTT / 2)) net.vm['gw'].netcall('tc qdisc add dev ethx root netem delay %dms' % (RTT / 2)) return net LATENCY = { 'connect_none': 3 * RTT, 'connect_basic': 3 * RTT, 'connect_digest': 3 * RTT, 'socks5_none': 4 * RTT, 'socks5_auth': 5 * RTT, 'regw_direct': 2 * RTT, } def heatup(vm): vm.do('curl -o /dev/null http://10.0.1.80/') # heatup L2 and auth caches def http_ping(vm): s = vm.do('curl -sS -w %{{time_connect}}/%{{time_total}}/%{{http_code}}/%{{size_download}} -o /dev/null http://10.0.1.80/') connect, total, code, size = s.split('/') connect, total, code, size = float(connect) * 1000, float(total) * 1000, int(code), int(size) return connect, total, code, size @pytest.mark.parametrize('tank', set(conftest.TANKS) & set(LATENCY)) def test_latency_tank(slow_net, tank): vm = slow_net.vm['tank%d' % conftest.TANKS[tank]] heatup(vm) connect, total, code, size = http_ping(vm) assert code == 200 and size == 612 assert connect < 0.005 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 def test_latency_regw(slow_net): vm, tank = slow_net.vm['regw'], 'regw_direct' heatup(vm) connect, total, code, size = http_ping(vm) assert code == 200 and size == 612 assert RTT*.8 < connect and connect < RTT*1.2 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 def test_nonce_reuse(slow_net): """ nonce reuse works and has no latency penalty """ tank = 'connect_digest' vm = slow_net.vm['tank%d' % conftest.TANKS[tank]] heatup(vm) begin = time.time() s = conftest.pmap([partial(http_ping, vm) for _ in range(5)]) total_sum = time.time() - begin for connect, total, code, size in s: assert code == 200 and size == 612 assert connect < 0.005 and LATENCY[tank]-RTT*.2 < total and total < LATENCY[tank]+RTT*.2 assert total_sum < total * 1.5 @pytest.mark.parametrize('tank, delay', [(t, d) for t in set(conftest.TANKS) & set(LATENCY) for d in [_*0.001 for _ in range(1, LATENCY[t]+RTT, RTT/2)] ] + [ ('httperr_proxy_refuse', (1 + 0*RTT/2) * 0.001), ('httperr_proxy_refuse', (1 + 1*RTT/2) * 0.001), ('httperr_proxy_refuse', (1 + 2*RTT/2) * 0.001), ('httperr_proxy_refuse', (1 + 3*RTT/2) * 0.001), ('httperr_proxy_timeout', (1 + 3*RTT/2) * 0.001), ]) def test_impatient_client(slow_net, tank, delay): vm, regw = slow_net.vm['tank%d' % conftest.TANKS[tank]], slow_net.vm['regw'] before, start = regw.lsfd(), time.time() try: page = vm.do('curl --max-time %s http://10.0.1.80/1M' % delay) #assert 'Welcome to nginx!' in page except CalledProcessError, e: if tank == 'httperr_proxy_refuse': assert e.returncode in (28, 52) # Operation timeout / Empty reply else: assert e.returncode == 28 # Operation timeout curl_time = time.time() - start assert curl_time < delay + RTT*0.001 # sanity check time.sleep( (LATENCY.get(tank, delay*1000) + 4*RTT)*0.001 ) if tank == 'httperr_proxy_timeout': time.sleep(135) # default connect() timeout is long assert before == regw.lsfd() # no leaks @pytest.fixture(scope='function', params=range(80, 60, -1)) def lowfd_net(request, net): def close(): net.vm['regw'].call('sudo ./prlimit-nofile {pid} 1024') request.addfinalizer(close) net.vm['regw'].call('sudo ./prlimit-nofile {pid} %d' % request.param) return net def test_accept_overflow(lowfd_net): tank = 'connect_none' vm, regw = lowfd_net.vm['tank%d' % conftest.TANKS[tank]], lowfd_net.vm['regw'] lsfd = regw.lsfd() year = str(time.gmtime().tm_year) discard_cmd = vm.fmt('sudo docker exec --interactive {sha} nc 10.0.1.13 discard') daytime_cmd = vm.fmt('sudo docker exec --interactive {sha} nc 10.0.1.13 daytime') dtstart = time.time() time.sleep(0.5) proc = [Popen(discard_cmd, stdin=PIPE, stdout=PIPE) for i in xrange(7)] time.sleep(0.5) dt = Popen(daytime_cmd, stdin=PIPE, stdout=PIPE) time.sleep(0.5) logs = regw.logs(since=dtstart) if any(['Too many open files' in _[1] for _ in logs]): # anything may happen, except leaks proc[0].communicate('/dev/null\x0d\x0a') dt.communicate('') time.sleep(1) for p in proc[1:]: p.communicate('/dev/null\x0d\x0a') else: assert any(['reached redsocks_conn_max limit' in _[1] for _ in logs]) # RLIMIT_NOFILE was not hit, this `daytime` call actually returns data daytime, _ = dt.communicate('') assert year in daytime and dt.returncode == 0 time.sleep(0.5) dtdeath = time.time() proc.append(Popen(discard_cmd, stdin=PIPE, stdout=PIPE)) time.sleep(0.5) logs = regw.logs(since=dtdeath) assert any(['reached redsocks_conn_max limit' in _[1] for _ in logs]) time.sleep(0.5) dt = Popen(daytime_cmd, stdin=PIPE, stdout=PIPE) time.sleep(0.5) assert all([p.poll() is None for p in proc]) and dt.poll() is None # processes are running out, _ = proc[0].communicate('/dev/null\x0d\x0a') daytime, _ = dt.communicate('') assert year in daytime and dt.returncode == 0 time.sleep(1) for p in proc[1:]: out, _ = p.communicate('/dev/null\x0d\x0a') assert out == '' time.sleep(1) assert lsfd == regw.lsfd() # no leaks redsocks-release-0.5/tests/build0000755000175000017500000000031313030520742016665 0ustar apoikosapoikos#!/bin/bash -ex (cd .. && make) cp -a ../redsocks ./regw/redsocks for img in *; do if [ -d "$img" -a -f "$img/Dockerfile" ]; then sudo docker build -t redsocks/"$img" ./"$img"/ fi done redsocks-release-0.5/tests/cleanup0000755000175000017500000000020713030520742017217 0ustar apoikosapoikos#!/bin/bash -x docker ps vms=$(echo gw web inetd regw dante-{0..1} squid-{8..9} tank{10..26}) docker stop --time 1 $vms docker rm $vms redsocks-release-0.5/http-auth.c0000644000175000017500000001742013030520742016567 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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. * * * http-auth library, provide basic and digest scheme * see RFC 2617 for details */ #include #include #include #include #include "md5.h" #include "base64.h" #include "log.h" #include "http-auth.h" char* basic_authentication_encode(const char *user, const char *passwd) { /* prepare the user:pass key pair */ int pair_len = strlen(user) + 1 + strlen(passwd); char pair[pair_len + 1]; sprintf(pair, "%s:%s", user, passwd); /* calculate the final string length */ int basic_len = BASE64_SIZE(pair_len); char *basic_ptr = calloc(basic_len + 1, 1); if (!base64_encode(basic_ptr, basic_len, (const uint8_t*)pair, pair_len)) return NULL; return basic_ptr; } #define MD5_HASHLEN (16) static void dump_hash(char *buf, const unsigned char *hash) { int i; for (i = 0; i < MD5_HASHLEN; i++) { buf += sprintf(buf, "%02x", hash[i]); } *buf = 0; } typedef struct { const char *b, *e; } param_token; /* Extract a parameter from the string (typically an HTTP header) at **SOURCE and advance SOURCE to the next parameter. Return false when there are no more parameters to extract. The name of the parameter is returned in NAME, and the value in VALUE. If the parameter has no value, the token's b==e.*/ static int extract_param(const char **source, param_token *name, param_token *value, char separator) { const char *p = *source; while (isspace (*p)) ++p; if (!*p) { *source = p; return 0; /* no error; nothing more to extract */ } /* Extract name. */ name->b = p; while (*p && !isspace (*p) && *p != '=' && *p != separator) ++p; name->e = p; if (name->b == name->e) return 0; /* empty name: error */ while (isspace (*p)) ++p; if (*p == separator || !*p) /* no value */ { value->b = value->e = ""; if (*p == separator) ++p; *source = p; return 1; } if (*p != '=') return 0; /* error */ /* *p is '=', extract value */ ++p; while (isspace (*p)) ++p; if (*p == '"') /* quoted */ { value->b = ++p; while (*p && *p != '"') ++p; if (!*p) return 0; value->e = p++; /* Currently at closing quote; find the end of param. */ while (isspace (*p)) ++p; while (*p && *p != separator) ++p; if (*p == separator) ++p; else if (*p) /* garbage after closed quote, e.g. foo="bar"baz */ return 0; } else /* unquoted */ { value->b = p; while (*p && *p != separator) ++p; value->e = p; while (value->e != value->b && isspace (value->e[-1])) --value->e; if (*p == separator) ++p; } *source = p; return 1; } char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce) { char *realm = NULL, *opaque = NULL, *nonce = NULL, *qop = NULL; char nc[9]; sprintf(nc, "%08x", count); const char *ptr = line; param_token name, value; while (extract_param(&ptr, &name, &value, ',')) { int namelen = name.e - name.b; int valuelen = value.e - value.b; if (strncasecmp(name.b, "realm" , namelen) == 0) { strncpy(realm = calloc(valuelen + 1, 1), value.b, valuelen); realm[valuelen] = '\0'; } else if (strncasecmp(name.b, "opaque", namelen) == 0) { strncpy(opaque = calloc(valuelen + 1, 1), value.b, valuelen); opaque[valuelen] = '\0'; } else if (strncasecmp(name.b, "nonce" , namelen) == 0) { strncpy(nonce = calloc(valuelen + 1, 1), value.b, valuelen); nonce[valuelen] = '\0'; } else if (strncasecmp(name.b, "qop" , namelen) == 0) { strncpy(qop = calloc(valuelen + 1, 1), value.b, valuelen); qop[valuelen] = '\0'; } } if (!realm || !nonce || !user || !passwd || !path || !method) { free(realm); free(opaque); free(nonce); free(qop); return NULL; } if (qop && strncasecmp(qop, "auth", 5) != 0) { /* FIXME: currently don't support auth-int, only "auth" is supported */ free(realm); free(opaque); free(nonce); free(qop); return NULL; } /* calculate the digest value */ md5_state_t ctx; md5_byte_t hash[MD5_HASHLEN]; char a1buf[MD5_HASHLEN * 2 + 1], a2buf[MD5_HASHLEN * 2 + 1]; char response[MD5_HASHLEN * 2 + 1]; /* A1 = username-value ":" realm-value ":" passwd */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)user, strlen(user)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)realm, strlen(realm)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)passwd, strlen(passwd)); md5_finish(&ctx, hash); dump_hash(a1buf, hash); /* A2 = Method ":" digest-uri-value */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)method, strlen(method)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)path, strlen(path)); md5_finish(&ctx, hash); dump_hash(a2buf, hash); /* qop set: request-digest = H(A1) ":" nonce-value ":" nc-value ":" cnonce-value ":" qop-value ":" H(A2) */ /* not set: request-digest = H(A1) ":" nonce-value ":" H(A2) */ md5_init(&ctx); md5_append(&ctx, (md5_byte_t*)a1buf, strlen(a1buf)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)nonce, strlen(nonce)); md5_append(&ctx, (md5_byte_t*)":", 1); if (qop) { md5_append(&ctx, (md5_byte_t*)nc, strlen(nc)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)cnonce, strlen(cnonce)); md5_append(&ctx, (md5_byte_t*)":", 1); md5_append(&ctx, (md5_byte_t*)qop, strlen(qop)); md5_append(&ctx, (md5_byte_t*)":", 1); } md5_append(&ctx, (md5_byte_t*)a2buf, strlen(a2buf)); md5_finish(&ctx, hash); dump_hash(response, hash); /* prepare the final string */ int len = 256; len += strlen(user); len += strlen(realm); len += strlen(nonce); len += strlen(path); len += strlen(response); if (qop) { len += strlen(qop); len += strlen(nc); len += strlen(cnonce); } if (opaque) { len += strlen(opaque); } char *res = (char*)malloc(len); if (!qop) { sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", user, realm, nonce, path, response); } else { sprintf(res, "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", qop=%s, nc=%s, cnonce=\"%s\"", user, realm, nonce, path, response, qop, nc, cnonce); } if (opaque) { char *p = res + strlen(res); strcat (p, ", opaque=\""); strcat (p, opaque); strcat (p, "\""); } free(realm); free(opaque); free(nonce); free(qop); return res; } const char *auth_request_header = "Proxy-Authenticate:"; const char *auth_response_header = "Proxy-Authorization:"; char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee) { char *line; for (;;) { line = redsocks_evbuffer_readline(src); if (tee && line) { if (evbuffer_add(tee, line, strlen(line)) != 0 || evbuffer_add(tee, "\r\n", 2) != 0) { log_error(LOG_NOTICE, "evbuffer_add"); free(line); return NULL; // I'm going up straight to the 403... } } // FIXME: multi-line headers are not supported if (line == NULL || *line == '\0' || strchr(line, ':') == NULL) { free(line); return NULL; } if (strncasecmp(line, auth_request_header, strlen(auth_request_header)) == 0) return line; free(line); } } redsocks-release-0.5/doc/0000755000175000017500000000000013030520742015246 5ustar apoikosapoikosredsocks-release-0.5/README0000644000175000017500000001643313030520742015370 0ustar apoikosapoikosThis tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection is system-wide. Why is that useful? I can suggest following reasons: * you use tor[1] and don't want any TCP connection to leak. * you use DVB ISP and this ISP provides internet connectivity with some special daemon that may be also called "Internet accelerator" and this accelerator acts as proxy. Globax[2] is example of such an accelerator. Linux/iptables, OpenBSD/pf and FreeBSD/ipfw are supported. Linux/iptables is well-tested, other implementations may have bugs, your bugreports are welcome. Transocks[3] is alike project but it has noticable performance penality. Transsocks_ev[4] is alike project too, but it has no HTTPS-proxy support and does not support authentication. Several Android apps also use redsocks under-the-hood: ProxyDroid[5][6] and sshtunnel[7][8]. And that's over 100'000 downloads! Wow! [1] http://www.torproject.org [2] http://www.globax.biz [3] http://transocks.sourceforge.net/ [4] http://oss.tiggerswelt.net/transocks_ev/ [5] http://code.google.com/p/proxydroid/ [6] https://market.android.com/details?id=org.proxydroid [7] http://code.google.com/p/sshtunnel/ [8] https://market.android.com/details?id=org.sshtunnel Another related issue is DNS over TCP. Redsocks includes `dnstc' that is fake and really dumb DNS server that returns "truncated answer" to every query via UDP. RFC-compliant resolver should repeat same query via TCP in this case - so the request can be redirected using usual redsocks facilities. Known compliant resolvers are: * bind9 (server) * dig, nslookup (tools based on bind9 code) Known non-compliant resolvers are: * eglibc resolver fails without any attempt to send request via TCP * powerdns-recursor can't properly startup without UDP connectivity as it can't load root hints On the other hand, DNS via TCP using bind9 may be painfully slow. If your bind9 setup is really slow, you have at least two options: pdnsd[9] caching server can run in TCP-only mode, ttdnsd[10][11] has no cache but can be useful for same purpose. [9] http://www.phys.uu.nl/~rombouts/pdnsd.html [10] http://www.mulliner.org/collin/ttdnsd.php [11] https://gitweb.torproject.org/ioerror/ttdnsd.git Features ======== Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) proxy server. Login/password authentication is supported for SOCKS5/HTTPS connections. SOCKS4 supports only username, password is ignored. for HTTPS, currently only Basic and Digest scheme is supported. Redirect UDP packets via SOCKS5 proxy server. NB: UDP still goes via UDP, so you can't relay UDP via OpenSSH. Sends "truncated reply" as an answer to UDP DNS queries. Redirect any HTTP connection to proxy that does not support transparent proxying (e.g. old SQUID had broken `acl myport' for such connections). License ======= All source code is licensed under Apache 2.0 license. You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html Packages ======== * Archlinux: https://aur.archlinux.org/packages/redsocks-git * Debian: http://packages.debian.org/search?searchon=names&keywords=redsocks * Gentoo (pentoo overlay): https://code.google.com/p/pentoo/source/browse/portage/trunk/net-proxy/redsocks * Gentoo (theebuilds overlay): http://code.google.com/p/theebuilds/source/browse/trunk/net-misc/redsocks * Gentoo (zugaina overlay): http://gpo.zugaina.org/net-proxy/redsocks * Ubuntu: http://packages.ubuntu.com/search?searchon=names&keywords=redsocks Compilation =========== libevent-2.0.x[5] is required. gcc and clang are supported right now, other compilers can be used but may require some code changes. Compilation is as easy as running `make', there is no `./configure' magic. GNU Make works, other implementations of make were not tested. [5] http://libevent.org/ || http://www.monkey.org/~provos/libevent/ Running ======= Program has following command-line options: -c sets proper path to config file ("./redsocks.conf" is default one) -t tests config file syntax -p set a file to write the getpid() into Following signals are understood: SIGUSR1 dumps list of connected clients to log SIGTERM and SIGINT terminates daemon, all active connections are closed You can see configuration file example in redsocks.conf.example iptables example ================ You have to build iptables with connection tracking and REDIRECT target. # Create new chain root# iptables -t nat -N REDSOCKS # Ignore LANs and some other reserved addresses. # See http://en.wikipedia.org/wiki/Reserved_IP_addresses#Reserved_IPv4_addresses # and http://tools.ietf.org/html/rfc5735 for full list of reserved networks. root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN # Anything else should be redirected to port 12345 root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345 # Any tcp connection made by `luser' should be redirected. root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS # You can also control that in more precise way using `gid-owner` from # iptables. root# groupadd socksified root# usermod --append --groups socksified luser root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS # Now you can launch your specific application with GID `socksified` and it # will be... socksified. See following commands (numbers may vary). # Note: you may have to relogin to apply `usermod` changes. luser$ id uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified) luser$ sg socksified -c id uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified) luser$ sg socksified -c "firefox" # If you want to configure socksifying router, you should look at # doc/iptables-packet-flow.png, doc/iptables-packet-flow-ng.png and # https://en.wikipedia.org/wiki/File:Netfilter-packet-flow.svg # Note, you should have proper `local_ip' value to get external packets with # redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding # REDIRECT target for details. # Depending on your network configuration iptables conf. may be as easy as: root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS Note about GID-based redirection ======== Keep in mind, that changed GID affects filesystem permissions, so if your application creates some files, the files will be created with luser:socksified owner/group. So, if you're not the only user in the group `socksified` and your umask allows to create group-readable files and your directory permissions, and so on, blah-blah, etc. THEN you may expose your files to another user. Ok, you have been warned. Homepage ======== http://darkk.net.ru/redsocks/ Mailing list: redsocks@librelist.com Mailing list also has archives[1]. [1] http://librelist.com/browser/redsocks/ TODO ==== Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those firewall types. Author ====== This program was written by Leonid Evdokimov redsocks-release-0.5/log.c0000644000175000017500000001312713030520742015432 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "utils.h" #include "log.h" const char *error_lowmem = ""; typedef void (*log_func)(const char *file, int line, const char *func, int priority, const char *message, const char *appendix); static const char* getprioname(int priority) { switch (priority) { case LOG_EMERG: return "emerg"; case LOG_ALERT: return "alert"; case LOG_CRIT: return "crit"; case LOG_ERR: return "err"; case LOG_WARNING: return "warning"; case LOG_NOTICE: return "notice"; case LOG_INFO: return "info"; case LOG_DEBUG: return "debug"; default: return "?"; } } static void fprint_timestamp( FILE* fd, const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { struct timeval tv = { }; gettimeofday(&tv, 0); /* XXX: there is no error-checking, IMHO it's better to lose messages * then to die and stop service */ const char* sprio = getprioname(priority); if (appendix) fprintf(fd, "%lu.%6.6lu %s %s:%u %s(...) %s: %s\n", tv.tv_sec, tv.tv_usec, sprio, file, line, func, message, appendix); else fprintf(fd, "%lu.%6.6lu %s %s:%u %s(...) %s\n", tv.tv_sec, tv.tv_usec, sprio, file, line, func, message); } static void stderr_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { fprint_timestamp(stderr, file, line, func, priority, message, appendix); } static FILE *logfile = NULL; static void logfile_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { fprint_timestamp(logfile, file, line, func, priority, message, appendix); fflush(logfile); } static void syslog_msg(const char *file, int line, const char *func, int priority, const char *message, const char *appendix) { if (appendix) syslog(priority, "%s: %s\n", message, appendix); else syslog(priority, "%s\n", message); } static log_func log_msg = stderr_msg; static log_func log_msg_next = NULL; static bool should_log_info = true; static bool should_log_debug = false; bool should_log(int priority) { return (priority != LOG_DEBUG && priority != LOG_INFO) || (priority == LOG_DEBUG && should_log_debug) || (priority == LOG_INFO && should_log_info); } int log_preopen(const char *dst, bool log_debug, bool log_info) { const char *syslog_prefix = "syslog:"; const char *file_prefix = "file:"; should_log_debug = log_debug; should_log_info = log_info; if (strcmp(dst, "stderr") == 0) { log_msg_next = stderr_msg; } else if (strncmp(dst, syslog_prefix, strlen(syslog_prefix)) == 0) { const char *facility_name = dst + strlen(syslog_prefix); int facility = -1; int logmask; struct { char *name; int value; } *ptpl, tpl[] = { { "daemon", LOG_DAEMON }, { "local0", LOG_LOCAL0 }, { "local1", LOG_LOCAL1 }, { "local2", LOG_LOCAL2 }, { "local3", LOG_LOCAL3 }, { "local4", LOG_LOCAL4 }, { "local5", LOG_LOCAL5 }, { "local6", LOG_LOCAL6 }, { "local7", LOG_LOCAL7 }, }; FOREACH(ptpl, tpl) if (strcmp(facility_name, ptpl->name) == 0) { facility = ptpl->value; break; } if (facility == -1) { log_error(LOG_ERR, "log_preopen(%s, ...): unknown syslog facility", dst); return -1; } openlog("redsocks", LOG_NDELAY | LOG_PID, facility); logmask = setlogmask(0); if (!log_debug) logmask &= ~(LOG_MASK(LOG_DEBUG)); if (!log_info) logmask &= ~(LOG_MASK(LOG_INFO)); setlogmask(logmask); log_msg_next = syslog_msg; } else if (strncmp(dst, file_prefix, strlen(file_prefix)) == 0) { const char *filename = dst + strlen(file_prefix); if ((logfile = fopen(filename, "a")) == NULL) { log_error(LOG_ERR, "log_preopen(%s, ...): %s", dst, strerror(errno)); return -1; } log_msg_next = logfile_msg; /* TODO: add log rotation */ } else { log_error(LOG_ERR, "log_preopen(%s, ...): unknown destination", dst); return -1; } return 0; } void log_open() { log_msg = log_msg_next; log_msg_next = NULL; } void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap) { if (!should_log(priority)) return; int saved_errno = errno; struct evbuffer *buff = evbuffer_new(); const char *message; if (buff) { evbuffer_add_vprintf(buff, fmt, ap); message = (const char*)evbuffer_pullup(buff, -1); } else message = error_lowmem; log_msg(file, line, func, priority, message, do_errno ? strerror(saved_errno) : NULL); if (buff) evbuffer_free(buff); } void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) { if (!should_log(priority)) return; va_list ap; va_start(ap, fmt); _log_vwrite(file, line, func, do_errno, priority, fmt, ap); va_end(ap); } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/http-connect.c0000644000175000017500000002343013030520742017255 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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. * * * http-connect upstream module for redsocks */ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "redsocks.h" #include "http-auth.h" typedef enum httpc_state_t { httpc_new, httpc_request_sent, httpc_reply_came, // 200 OK came, skipping headers... httpc_headers_skipped, // starting pump! httpc_no_way, // proxy can't handle the request httpc_MAX, } httpc_state; #define HTTP_HEAD_WM_HIGH 4096 // that should be enough for one HTTP line. static void httpc_client_init(redsocks_client *client) { client->state = httpc_new; } static void httpc_instance_fini(redsocks_instance *instance) { http_auth *auth = red_http_auth(instance); free(auth->last_auth_query); auth->last_auth_query = NULL; } static struct evbuffer *httpc_mkconnect(redsocks_client *client); extern const char *auth_request_header; extern const char *auth_response_header; static void httpc_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(client->relay == buffev); assert(client->state == httpc_request_sent || client->state == httpc_reply_came); redsocks_touch_client(client); // evbuffer_add() triggers callbacks, so we can't write to client->client // till we know that we're going to ONFAIL_FORWARD_HTTP_ERR. // And the decision is made when all the headers are processed. struct evbuffer* tee = NULL; const bool do_errtee = client->instance->config.on_proxy_fail == ONFAIL_FORWARD_HTTP_ERR; if (client->state == httpc_request_sent) { size_t len = evbuffer_get_length(buffev->input); char *line = redsocks_evbuffer_readline(buffev->input); if (line) { unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match if (code == 407) { // auth failed http_auth *auth = red_http_auth(client->instance); if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line); client->state = httpc_no_way; } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no login/password configured: %s", line); client->state = httpc_no_way; } else { if (do_errtee) tee = evbuffer_new(); char *auth_request = http_auth_request_header(buffev->input, tee); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line); client->state = httpc_no_way; } else { free(line); if (tee) evbuffer_free(tee); free(auth->last_auth_query); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; size_t last_auth_query_len = strlen(ptr) + 1; auth->last_auth_query = calloc(last_auth_query_len, 1); memcpy(auth->last_auth_query, ptr, last_auth_query_len); auth->last_auth_count = 0; free(auth_request); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return; } /* close relay tunnel */ redsocks_bufferevent_free(client->relay); /* set to initial state*/ client->state = httpc_new; /* and reconnect */ redsocks_connect_relay(client); return; } } } else if (200 <= code && code <= 299) { client->state = httpc_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy error: %s", line); client->state = httpc_no_way; } } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line); client->state = httpc_no_way; } if (do_errtee && client->state == httpc_no_way) { if (bufferevent_write(client->client, line, strlen(line)) != 0 || bufferevent_write(client->client, "\r\n", 2) != 0) { redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write"); goto fail; } } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy reply is too long, %zu bytes", len); client->state = httpc_no_way; } } if (do_errtee && client->state == httpc_no_way) { if (tee) { if (bufferevent_write_buffer(client->client, tee) != 0) { redsocks_log_errno(client, LOG_NOTICE, "bufferevent_write_buffer"); goto fail; } } redsocks_shutdown(client, client->client, SHUT_RD); const size_t avail = evbuffer_get_length(client->client->input); if (avail) { if (evbuffer_drain(client->client->input, avail) != 0) { redsocks_log_errno(client, LOG_NOTICE, "evbuffer_drain"); goto fail; } } redsocks_shutdown(client, client->relay, SHUT_WR); client->state = httpc_headers_skipped; } fail: if (tee) { evbuffer_free(tee); } if (client->state == httpc_no_way) { redsocks_drop_client(client); return; } while (client->state == httpc_reply_came) { char *line = redsocks_evbuffer_readline(buffev->input); if (line) { if (strlen(line) == 0) { client->state = httpc_headers_skipped; } free(line); } else { break; } } if (client->state == httpc_headers_skipped) { redsocks_start_relay(client); } } static struct evbuffer *httpc_mkconnect(redsocks_client *client) { struct evbuffer *buff = NULL, *retval = NULL; char *auth_string = NULL; int len; buff = evbuffer_new(); if (!buff) { redsocks_log_errno(client, LOG_ERR, "evbuffer_new"); goto fail; } http_auth *auth = red_http_auth(client->instance); ++auth->last_auth_count; const char *auth_scheme = NULL; if (auth->last_auth_query != NULL) { /* find previous auth challange */ if (strncasecmp(auth->last_auth_query, "Basic", 5) == 0) { auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0) { /* calculate uri */ char uri[128]; snprintf(uri, 128, "%s:%u", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); /* prepare an random string for cnounce */ char cnounce[17]; snprintf(cnounce, sizeof(cnounce), "%08x%08x", red_randui32(), red_randui32()); auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line client->instance->config.login, client->instance->config.password, //user, pass "CONNECT", uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce auth_scheme = "Digest"; } } // TODO: do accurate evbuffer_expand() while cleaning up http-auth len = evbuffer_add_printf(buff, "CONNECT %s:%u HTTP/1.0\r\n", inet_ntoa(client->destaddr.sin_addr), ntohs(client->destaddr.sin_port)); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); goto fail; } if (auth_string) { len = evbuffer_add_printf(buff, "%s %s %s\r\n", auth_response_header, auth_scheme, auth_string); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); goto fail; } free(auth_string); auth_string = NULL; } const enum disclose_src_e disclose_src = client->instance->config.disclose_src; if (disclose_src != DISCLOSE_NONE) { char clientip[INET_ADDRSTRLEN]; const char *ip = inet_ntop(client->clientaddr.sin_family, &client->clientaddr.sin_addr, clientip, sizeof(clientip)); if (!ip) { redsocks_log_errno(client, LOG_ERR, "inet_ntop"); goto fail; } if (disclose_src == DISCLOSE_X_FORWARDED_FOR) { len = evbuffer_add_printf(buff, "X-Forwarded-For: %s\r\n", ip); } else if (disclose_src == DISCLOSE_FORWARDED_IP) { len = evbuffer_add_printf(buff, "Forwarded: for=%s\r\n", ip); } else if (disclose_src == DISCLOSE_FORWARDED_IPPORT) { len = evbuffer_add_printf(buff, "Forwarded: for=\"%s:%d\"\r\n", ip, ntohs(client->clientaddr.sin_port)); } if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add_printf"); goto fail; } } len = evbuffer_add(buff, "\r\n", 2); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "evbufer_add"); goto fail; } retval = buff; buff = NULL; fail: if (auth_string) free(auth_string); if (buff) evbuffer_free(buff); return retval; } static void httpc_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == httpc_new) { redsocks_write_helper_ex( buffev, client, httpc_mkconnect, httpc_request_sent, 1, HTTP_HEAD_WM_HIGH ); } else if (client->state >= httpc_request_sent) { bufferevent_disable(buffev, EV_WRITE); } } relay_subsys http_connect_subsys = { .name = "http-connect", .payload_len = 0, .instance_payload_len = sizeof(http_auth), .readcb = httpc_read_cb, .writecb = httpc_write_cb, .init = httpc_client_init, .instance_fini = httpc_instance_fini, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/reddns.c0000644000175000017500000000507513030520742016133 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "parser.h" #include "main.h" typedef struct reddns_config_t reddns_config; struct reddns_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; struct in_addr fakenet; struct in_addr fakenetmask; }; int reddns_onenter(parser_section *section) { reddns_config *conf = calloc(1, sizeof(reddns_config)); section->data = conf; section->entries[0].addr = &conf->bindaddr.sin_port; section->entries[1].addr = &conf->fakenet; section->entries[2].addr = &conf->fakenetmask; section->entries[3].addr = &conf->relayaddr.sin_addr; section->entries[4].addr = &conf->relayaddr.sin_port; fprintf(stderr, "%s\n", __FUNCTION__); return 0; } int reddns_onexit(parser_section *section) { reddns_config *conf = section->data; fprintf(stderr, "%s {\n" "local_port = %u;\n" "fakeip_net = %s/%s;\n" "ip = %s;\n" "port = %u;\n" "}\n", __FUNCTION__, conf->bindaddr.sin_port, strdup(inet_ntoa(conf->fakenet)), strdup(inet_ntoa(conf->fakenetmask)), strdup(inet_ntoa(conf->relayaddr.sin_addr)), conf->relayaddr.sin_port ); return 0; } parser_entry reddns_entries[] = { { .key = "local_port", .type = pt_uint16 }, { .key = "fakeip_net", .type = pt_in_addr2 }, { .key = "fakeip_netmask", .type = pt_in_addr }, { .key = "ip", .type = pt_in_addr }, { .key = "port", .type = pt_uint16 }, { } }; parser_section reddns_conf_section = { .name = "reddns", .entries = reddns_entries, .onenter = reddns_onenter, .onexit = reddns_onexit }; int reddns_init() { #error It's non-working stub at the moment. return 0; } int reddns_fini() { return 0; } app_subsys reddns_subsys = { .init = reddns_init, .fini = reddns_fini, .conf_section = &reddns_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/base64.c0000644000175000017500000000614213030520742015734 0ustar apoikosapoikos/* * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) * * This file is part of FFmpeg. * * FFmpeg 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. * * FFmpeg 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 FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file * @brief Base64 encode/decode * @author Ryan Martell (with lots of Michael) */ #include #include "utils.h" #include "base64.h" /* ---------------- private code */ static const uint8_t map2[] = { 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 }; int base64_decode(uint8_t *out, const char *in, int out_size) { int i, v; uint8_t *dst = out; v = 0; for (i = 0; in[i] && in[i] != '='; i++) { unsigned int index= in[i]-43; if (index>=SIZEOF_ARRAY(map2) || map2[index] == 0xff) return -1; v = (v << 6) + map2[index]; if (i & 3) { if (dst - out < out_size) { *dst++ = v >> (6 - 2 * (i & 3)); } } } return dst - out; } /***************************************************************************** * b64_encode: Stolen from VLC's http.c. * Simplified by Michael. * Fixed edge cases and made it work from data (vs. strings) by Ryan. *****************************************************************************/ char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *ret, *dst; unsigned i_bits = 0; int i_shift = 0; int bytes_remaining = in_size; if (in_size >= UINT_MAX / 4 || out_size < BASE64_SIZE(in_size)) return NULL; ret = dst = out; while (bytes_remaining) { i_bits = (i_bits << 8) + *in++; bytes_remaining--; i_shift += 8; do { *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; i_shift -= 6; } while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); } while ((dst - ret) & 3) *dst++ = '='; *dst = '\0'; return ret; } redsocks-release-0.5/README.html0000644000175000017500000002443013030520742016327 0ustar apoikosapoikos redsocks - transparent socks redirector

redsocks - transparent socks redirector


This tool allows you to redirect any TCP connection to SOCKS or HTTPS proxy using your firewall, so redirection is system-wide.

Why is that useful? I can suggest following reasons:

  • you use tor and don't want any TCP connection to leak
  • you use DVB ISP and this ISP provides internet connectivity with some special daemon that may be also called "Internet accelerator" and this accelerator acts as proxy. Globax is example of such an accelerator

Linux/iptables, OpenBSD/pf and FreeBSD/ipfw are supported. Linux/iptables is well-tested, other implementations may have bugs, your bugreports are welcome.

Transocks is alike project but it has noticable performance penality.

Transsocks_ev is alike project too, but it has no HTTPS-proxy support and does not support authentication.

Several Andoird apps also use redsocks under-the-hood: ProxyDroid (@AndroidMarket) and sshtunnel (@AndroidMarket). And that's over 100'000 downloads! Wow!

Another related issue is DNS over TCP. Redsocks includes `dnstc' that is fake and really dumb DNS server that returns "truncated answer" to every query via UDP. RFC-compliant resolver should repeat same query via TCP in this case - so the request can be redirected using usual redsocks facilities.

Known compliant resolvers are:

  • bind9 (server)
  • dig, nslookup (tools based on bind9 code)

Known non-compliant resolvers are:

  • eglibc resolver fails without any attempt to send request via TCP
  • powerdns-recursor can't properly startup without UDP connectivity as it can't load root hints

On the other hand, DNS via TCP using bind9 may be painfully slow. If your bind9 setup is really slow, you have at least two options: pdnsd caching server can run in TCP-only mode, ttdnsd (git repo) has no cache but can be useful for same purpose.

Features

Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) proxy server.

Login/password authentication is supported for SOCKS5/HTTPS connections. SOCKS4 supports only username, password is ignored. for HTTPS, currently only Basic and Digest scheme is supported.

Redirect UDP packets via SOCKS5 proxy server. NB: UDP still goes via UDP, so you can't relay UDP via OpenSSH.

Sends "truncated reply" as an answer to UDP DNS queries.

Redirect any HTTP connection to proxy that does not support transparent proxying (e.g. old SQUID had broken `acl myport' for such connections).

License

All source code is licensed under Apache 2.0 license.

You can get a copy at http://www.apache.org/licenses/LICENSE-2.0.html

Packages

Compilation

libevent-2.0.x is required.

gcc is only supported compiler right now, other compilers can be used but may require some code changes.

Compilation is as easy as running `make', there is no `./configure' magic.

GNU Make works, other implementations of make were not tested.

Running

Program has following command-line options:
-c sets proper path to config file ("./redsocks.conf" is default one)
-t tests config file syntax
-p set a file to write the getpid() into

Following signals are understood:
SIGUSR1 dumps list of connected clients to log
SIGTERM and SIGINT terminates daemon, all active connections are closed

You can see configuration file example in redsocks.conf.example

iptables example

You have to build iptables with connection tracking and REDIRECT target.

# Create new chain
root# iptables -t nat -N REDSOCKS

# Ignore LANs and some other reserved addresses.
# See Wikipedia and RFC5735 for full list of reserved networks.
root# iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN
root# iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN
root# iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN
root# iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN
root# iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN
root# iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN

# Anything else should be redirected to port 12345
root# iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports 12345

# Any tcp connection made by `luser' should be redirected.
root# iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner luser -j REDSOCKS

# You can also control that in more precise way using `gid-owner` from
# iptables.
root# groupadd socksified
root# usermod --append --groups socksified luser
root# iptables -t nat -A OUTPUT -p tcp -m owner --gid-owner socksified -j REDSOCKS

# Now you can launch your specific application with GID `socksified` and it
# will be... socksified. See following commands (numbers may vary).
# Note: you may have to relogin to apply `usermod` changes.
luser$ id
uid=1000(luser) gid=1000(luser) groups=1000(luser),1001(socksified)
luser$ sg socksified -c id
uid=1000(luser) gid=1001(socksified) groups=1000(luser),1001(socksified)
luser$ sg socksified -c "firefox"

# If you want to configure socksifying router, you should look at
# doc/iptables-packet-flow.png and doc/iptables-packet-flow-ng.png and
# wikipedia/File:Netfilter-packet-flow.svg
# Note, you should have proper `local_ip' value to get external packets with
# redsocks, default 127.0.0.1 will not go. See iptables(8) manpage regarding
# REDIRECT target for details.
# Depending on your network configuration iptables conf. may be as easy as:
root# iptables -t nat -A PREROUTING --in-interface eth_int -p tcp -j REDSOCKS

Note about GID-based redirection

Keep in mind, that changed GID affects filesystem permissions, so if your application creates some files, the files will be created with luser:socksified owner/group. So, if you're not the only user in the group `socksified` and your umask allows to create group-readable files and your directory permissions, and so on, blah-blah, etc. THEN you may expose your files to another user.

Ok, you have been warned.

Homepage

Homepage: http://darkk.net.ru/redsocks/

Mailing list: redsocks@librelist.com

Mailing list also has archives.

TODO

  • Test OpenBSD (pf) and FreeBSD (ipfw) and write setup examples for those firewall types.

Author

This program was written by Leonid Evdokimov.
 (~~~~~~~~~~\   __
 | jabber:  )  /  \
 | mailto: (  /|oo \
  \_________\(_|  /_)
              _ @/_ \
             |     | \   \\
      leon   | (*) |  \   ))  darkk.net.ru
             |__U__| /  \//
              _//|| _\   /
             (_/(_|(____/
                         Valid CSS!
                         Valid XHTML 1.0 Transitional
redsocks-release-0.5/libevent-compat.h0000644000175000017500000000065413030520742017750 0ustar apoikosapoikos#ifndef UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E #define UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E /* evutil_socket_t is macros in libevent-2.0, not typedef, libevent-1.4 is * still supported because of Ubuntu 10.04 LTS */ #ifndef evutil_socket_t # warning Using hardcoded value for evutil_socket_t as libevent headers do not define it. # define evutil_socket_t int #endif #endif // FC148CFA_5ECC_488E_8A62_CD39406C9F1E redsocks-release-0.5/base.c0000644000175000017500000003341013030520742015560 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "config.h" #if defined USE_IPTABLES # include # include #endif #if defined USE_PF # include # include # include # include #endif #include "log.h" #include "main.h" #include "parser.h" #include "redsocks.h" typedef struct redirector_subsys_t { int (*init)(); void (*fini)(); int (*getdestaddr)(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); const char *name; // some subsystems may store data here: int private; } redirector_subsys; typedef struct base_instance_t { int configured; char *chroot; char *user; char *group; char *redirector_name; redirector_subsys *redirector; char *log_name; bool log_debug; bool log_info; bool daemon; #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPCNT) && defined(TCP_KEEPINTVL) uint16_t tcp_keepalive_time; uint16_t tcp_keepalive_probes; uint16_t tcp_keepalive_intvl; #endif uint32_t rlimit_nofile; uint32_t redsocks_conn_max; uint32_t connpres_idle_timeout; uint32_t max_accept_backoff_ms; } base_instance; static base_instance instance; #if defined __FreeBSD__ || defined USE_PF static int redir_open_private(const char *fname, int flags) { int fd = open(fname, flags); if (fd < 0) { log_errno(LOG_ERR, "open(%s)", fname); return -1; } instance.redirector->private = fd; return 0; } static void redir_close_private() { close(instance.redirector->private); instance.redirector->private = -1; } #endif #ifdef __FreeBSD__ static int redir_init_ipf() { #ifdef IPNAT_NAME const char *fname = IPNAT_NAME; #else const char *fname = IPL_NAME; #endif return redir_open_private(fname, O_RDONLY); } static int getdestaddr_ipf(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { int natfd = instance.redirector->private; struct natlookup natLookup; int x; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) struct ipfobj obj; #else static int siocgnatl_cmd = SIOCGNATL & 0xff; #endif #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) obj.ipfo_rev = IPFILTER_VERSION; obj.ipfo_size = sizeof(natLookup); obj.ipfo_ptr = &natLookup; obj.ipfo_type = IPFOBJ_NATLOOKUP; obj.ipfo_offset = 0; #endif natLookup.nl_inport = bindaddr->sin_port; natLookup.nl_outport = client->sin_port; natLookup.nl_inip = bindaddr->sin_addr; natLookup.nl_outip = client->sin_addr; natLookup.nl_flags = IPN_TCP; #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027) x = ioctl(natfd, SIOCGNATL, &obj); #else /* * IP-Filter changed the type for SIOCGNATL between * 3.3 and 3.4. It also changed the cmd value for * SIOCGNATL, so at least we can detect it. We could * put something in configure and use ifdefs here, but * this seems simpler. */ if (63 == siocgnatl_cmd) { struct natlookup *nlp = &natLookup; x = ioctl(natfd, SIOCGNATL, &nlp); } else { x = ioctl(natfd, SIOCGNATL, &natLookup); } #endif if (x < 0) { if (errno != ESRCH) log_errno(LOG_WARNING, "ioctl(SIOCGNATL)\n"); return -1; } else { destaddr->sin_family = AF_INET; destaddr->sin_port = natLookup.nl_realport; destaddr->sin_addr = natLookup.nl_realip; return 0; } } #endif #ifdef USE_PF static int redir_init_pf() { return redir_open_private("/dev/pf", O_RDWR); } static int getdestaddr_pf( int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { int pffd = instance.redirector->private; struct pfioc_natlook nl; int saved_errno; char clientaddr_str[INET6_ADDRSTRLEN], bindaddr_str[INET6_ADDRSTRLEN]; memset(&nl, 0, sizeof(struct pfioc_natlook)); nl.saddr.v4.s_addr = client->sin_addr.s_addr; nl.sport = client->sin_port; nl.daddr.v4.s_addr = bindaddr->sin_addr.s_addr; nl.dport = bindaddr->sin_port; nl.af = AF_INET; nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; if (ioctl(pffd, DIOCNATLOOK, &nl) != 0) { if (errno == ENOENT) { nl.direction = PF_IN; // required to redirect local packets if (ioctl(pffd, DIOCNATLOOK, &nl) != 0) { goto fail; } } else { goto fail; } } destaddr->sin_family = AF_INET; destaddr->sin_port = nl.rdport; destaddr->sin_addr = nl.rdaddr.v4; return 0; fail: saved_errno = errno; if (!inet_ntop(client->sin_family, &client->sin_addr, clientaddr_str, sizeof(clientaddr_str))) strncpy(clientaddr_str, "???", sizeof(clientaddr_str)); if (!inet_ntop(bindaddr->sin_family, &bindaddr->sin_addr, bindaddr_str, sizeof(bindaddr_str))) strncpy(bindaddr_str, "???", sizeof(bindaddr_str)); errno = saved_errno; log_errno(LOG_WARNING, "ioctl(DIOCNATLOOK {src=%s:%d, dst=%s:%d})", clientaddr_str, ntohs(nl.sport), bindaddr_str, ntohs(nl.dport)); return -1; } #endif #ifdef USE_IPTABLES static int getdestaddr_iptables(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; error = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, destaddr, &socklen); if (error) { log_errno(LOG_WARNING, "getsockopt"); return -1; } return 0; } #endif static int getdestaddr_generic(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { socklen_t socklen = sizeof(*destaddr); int error; error = getsockname(fd, (struct sockaddr*)destaddr, &socklen); if (error) { log_errno(LOG_WARNING, "getsockopt"); return -1; } return 0; } int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr) { return instance.redirector->getdestaddr(fd, client, bindaddr, destaddr); } int apply_tcp_keepalive(int fd) { struct { int level, option, value; } opt[] = { { SOL_SOCKET, SO_KEEPALIVE, 1 }, { IPPROTO_TCP, TCP_KEEPIDLE, instance.tcp_keepalive_time }, { IPPROTO_TCP, TCP_KEEPCNT, instance.tcp_keepalive_probes }, { IPPROTO_TCP, TCP_KEEPINTVL, instance.tcp_keepalive_intvl }, }; for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) { if (opt[i].value) { int error = setsockopt(fd, opt[i].level, opt[i].option, &opt[i].value, sizeof(opt[i].value)); if (error) { log_errno(LOG_WARNING, "setsockopt(%d, %d, %d, &%d, %zu)", fd, opt[i].level, opt[i].option, opt[i].value, sizeof(opt[i].value)); return -1; } } } return 0; } uint32_t max_accept_backoff_ms() { return instance.max_accept_backoff_ms; } uint32_t redsocks_conn_max() { return instance.redsocks_conn_max; } uint32_t connpres_idle_timeout() { return instance.connpres_idle_timeout; } static redirector_subsys redirector_subsystems[] = { #ifdef __FreeBSD__ { .name = "ipf", .init = redir_init_ipf, .fini = redir_close_private, .getdestaddr = getdestaddr_ipf }, #endif #ifdef USE_PF { .name = "pf", .init = redir_init_pf, .fini = redir_close_private, .getdestaddr = getdestaddr_pf }, #endif #ifdef USE_IPTABLES { .name = "iptables", .getdestaddr = getdestaddr_iptables }, #endif { .name = "generic", .getdestaddr = getdestaddr_generic }, }; /*********************************************************************** * `base` config parsing */ static parser_entry base_entries[] = { { .key = "chroot", .type = pt_pchar, .addr = &instance.chroot }, { .key = "user", .type = pt_pchar, .addr = &instance.user }, { .key = "group", .type = pt_pchar, .addr = &instance.group }, { .key = "redirector", .type = pt_pchar, .addr = &instance.redirector_name }, { .key = "log", .type = pt_pchar, .addr = &instance.log_name }, { .key = "log_debug", .type = pt_bool, .addr = &instance.log_debug }, { .key = "log_info", .type = pt_bool, .addr = &instance.log_info }, { .key = "daemon", .type = pt_bool, .addr = &instance.daemon }, #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPCNT) && defined(TCP_KEEPINTVL) { .key = "tcp_keepalive_time", .type = pt_uint16, .addr = &instance.tcp_keepalive_time }, { .key = "tcp_keepalive_probes", .type = pt_uint16, .addr = &instance.tcp_keepalive_probes }, { .key = "tcp_keepalive_intvl", .type = pt_uint16, .addr = &instance.tcp_keepalive_intvl }, #endif { .key = "rlimit_nofile", .type = pt_uint32, .addr = &instance.rlimit_nofile }, { .key = "redsocks_conn_max", .type = pt_uint32, .addr = &instance.redsocks_conn_max }, { .key = "connpres_idle_timeout", .type = pt_uint32, .addr = &instance.connpres_idle_timeout }, { .key = "max_accept_backoff", .type = pt_uint32, .addr = &instance.max_accept_backoff_ms }, { } }; static int base_onenter(parser_section *section) { if (instance.configured) { parser_error(section->context, "only one instance of base is valid"); return -1; } memset(&instance, 0, sizeof(instance)); instance.configured = 1; instance.max_accept_backoff_ms = 60000; instance.connpres_idle_timeout = 7440; return 0; } static int base_onexit(parser_section *section) { if (!instance.max_accept_backoff_ms) { parser_error(section->context, "`max_accept_backoff` must be positive, 0 ms is too low"); return -1; } if (instance.redirector_name) { redirector_subsys *ss; FOREACH(ss, redirector_subsystems) { if (!strcmp(ss->name, instance.redirector_name)) { instance.redirector = ss; instance.redirector->private = -1; break; } } if (!instance.redirector) { parser_error(section->context, "invalid `redirector` set <%s>", instance.redirector_name); return -1; } } else { parser_error(section->context, "no `redirector` set"); return -1; } return 0; } static parser_section base_conf_section = { .name = "base", .entries = base_entries, .onenter = base_onenter, .onexit = base_onexit }; /*********************************************************************** * `base` initialization */ static int base_fini(); static int base_init() { uid_t uid = -1; gid_t gid = -1; int devnull = -1; if (!instance.configured) { log_error(LOG_ERR, "there is no configured instance of `base`, check config file"); return -1; } if (instance.redirector->init && instance.redirector->init() < 0) return -1; if (instance.user) { struct passwd *pw = getpwnam(instance.user); if (pw == NULL) { log_errno(LOG_ERR, "getpwnam(%s)", instance.user); goto fail; } uid = pw->pw_uid; } if (instance.group) { struct group *gr = getgrnam(instance.group); if (gr == NULL) { log_errno(LOG_ERR, "getgrnam(%s)", instance.group); goto fail; } gid = gr->gr_gid; } if (log_preopen( instance.log_name ? instance.log_name : instance.daemon ? "syslog:daemon" : "stderr", instance.log_debug, instance.log_info ) < 0 ) { goto fail; } if (instance.rlimit_nofile) { struct rlimit rlmt; rlmt.rlim_cur = instance.rlimit_nofile; rlmt.rlim_max = instance.rlimit_nofile; if (setrlimit(RLIMIT_NOFILE, &rlmt) != 0) { log_errno(LOG_ERR, "setrlimit(RLIMIT_NOFILE, %u)", instance.rlimit_nofile); goto fail; } } else { struct rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) != 0) { log_errno(LOG_ERR, "getrlimit(RLIMIT_NOFILE)"); goto fail; } instance.rlimit_nofile = rlmt.rlim_cur; } if (!instance.redsocks_conn_max) { instance.redsocks_conn_max = (instance.rlimit_nofile - instance.rlimit_nofile / 4) / (redsocks_has_splice_instance() ? 6 : 2); } if (instance.daemon) { devnull = open("/dev/null", O_RDWR); if (devnull == -1) { log_errno(LOG_ERR, "open(\"/dev/null\", O_RDWR"); goto fail; } } if (instance.chroot) { if (chroot(instance.chroot) < 0) { log_errno(LOG_ERR, "chroot(%s)", instance.chroot); goto fail; } } if (instance.daemon || instance.chroot) { if (chdir("/") < 0) { log_errno(LOG_ERR, "chdir(\"/\")"); goto fail; } } if (instance.group) { if (setgid(gid) < 0) { log_errno(LOG_ERR, "setgid(%i)", gid); goto fail; } } if (instance.user) { if (setuid(uid) < 0) { log_errno(LOG_ERR, "setuid(%i)", uid); goto fail; } } if (instance.daemon) { switch (fork()) { case -1: // error log_errno(LOG_ERR, "fork()"); goto fail; case 0: // child break; default: // parent, pid is returned exit(EXIT_SUCCESS); } } log_open(); // child has nothing to do with TTY if (instance.daemon) { if (setsid() < 0) { log_errno(LOG_ERR, "setsid()"); goto fail; } int fds[] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO }; int *pfd; FOREACH(pfd, fds) if (dup2(devnull, *pfd) < 0) { log_errno(LOG_ERR, "dup2(devnull, %i)", *pfd); goto fail; } close(devnull); } return 0; fail: if (devnull != -1) close(devnull); base_fini(); return -1; } static int base_fini() { if (instance.redirector->fini) instance.redirector->fini(); free(instance.chroot); free(instance.user); free(instance.group); free(instance.redirector_name); free(instance.log_name); memset(&instance, 0, sizeof(instance)); return 0; } app_subsys base_subsys = { .init = base_init, .fini = base_fini, .conf_section = &base_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/tools/0000755000175000017500000000000013030520742015641 5ustar apoikosapoikosredsocks-release-0.5/tools/git-repack.sh0000755000175000017500000000474713030520742020242 0ustar apoikosapoikos#!/bin/bash set -o errexit set -o xtrace user="darkk" proj="redsocks" versions="0.1 0.2 0.3 0.4" for file in `python -c "import urllib2, json; print '\n'.join(d['name'] for d in json.load(urllib2.urlopen('https://api.github.com/repos/${user}/${proj}/downloads')))"`; do touch "$file.uploaded" done token="" for ver in $versions; do for pkg in tar.gz tar.bz2 tar.lzma; do case $pkg in tar.gz) sortof=gzip ;; tar.bz2) sortof=bzip2 ;; tar.lzma) sortof=lzma ;; esac tag="release-${ver}" gittar="github-$tag.tar.gz" gooddir="${proj}-${ver}" file="${proj}-${ver}.${pkg}" if [ -r "${file}.uploaded" ]; then continue fi rm -rf "${user}-${proj}-"* if [ ! -r "$file" ]; then if [ ! -d "$gooddir" ]; then if [ ! -f "$gittar" ]; then wget -O "$gittar" "https://github.com/${user}/${proj}/tarball/${tag}" fi tar --extract --file "$gittar" mv "${user}-${proj}-"* "$gooddir" fi tar "--${sortof}" --create --file "$file" "$gooddir" fi if [ -z "$token" ]; then if [ ! -r git-repack.sh.token ]; then curl --data '{"scopes": ["public_repo"], "note": "uploader script"}' -u "$user" "https://api.github.com/authorizations" \ | python -c "import sys, json, pipes; print 'token=%s' % pipes.quote(json.load(sys.stdin)['token'])" \ > git-repack.sh.token fi . git-repack.sh.token fi . <(python -c "import os, urllib2, json, pipes; print '\n'.join('GITHUB_%s=%s' % (k.replace('-', '_'), pipes.quote(str(v))) for k, v in json.load(urllib2.urlopen('https://api.github.com/repos/${user}/${proj}/downloads?access_token=${token}', data=json.dumps({'name': '${file}', 'size': os.stat('${file}').st_size, 'description': 'Version ${ver}, ${sortof}', 'content-type': 'application/x-gtar-compressed'}))).iteritems())") curl \ --include \ -F "key=${GITHUB_path}" -F "acl=${GITHUB_acl}" -F "success_action_status=201" \ -F "Filename=${GITHUB_name}" \ -F "AWSAccessKeyId=${GITHUB_accesskeyid}" -F "Policy=${GITHUB_policy}" -F "Signature=${GITHUB_signature}" \ -F "Content-Type=${GITHUB_mime_type}" \ -F "file=@${file}" https://github.s3.amazonaws.com/ echo; touch "${file}.uploaded" done done redsocks-release-0.5/redudp.c0000644000175000017500000006205413030520742016137 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "list.h" #include "log.h" #include "socks5.h" #include "parser.h" #include "main.h" #include "redsocks.h" #include "redudp.h" #include "libc-compat.h" #define redudp_log_error(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg) #define redudp_log_errno(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, get_destaddr(client), prio, ## msg) static void redudp_pkt_from_socks(int fd, short what, void *_arg); static void redudp_drop_client(redudp_client *client); static void redudp_fini_instance(redudp_instance *instance); static int redudp_fini(); static int redudp_transparent(int fd); typedef struct redudp_expected_assoc_reply_t { socks5_reply h; socks5_addr_ipv4 ip; } PACKED redudp_expected_assoc_reply; struct bound_udp4_key { struct in_addr sin_addr; uint16_t sin_port; }; struct bound_udp4 { struct bound_udp4_key key; int ref; int fd; }; /*********************************************************************** * Helpers */ // TODO: separate binding to privileged process (this operation requires uid-0) static void* root_bound_udp4 = NULL; // to avoid two binds to same IP:port static int bound_udp4_cmp(const void *a, const void *b) { return memcmp(a, b, sizeof(struct bound_udp4_key)); } static void bound_udp4_mkkey(struct bound_udp4_key *key, const struct sockaddr_in *addr) { memset(key, 0, sizeof(*key)); key->sin_addr = addr->sin_addr; key->sin_port = addr->sin_port; } static int bound_udp4_get(const struct sockaddr_in *addr) { struct bound_udp4_key key; struct bound_udp4 *node, **pnode; bound_udp4_mkkey(&key, addr); // I assume, that memory allocation for lookup is awful, so I use // tfind/tsearch pair instead of tsearch/check-result. pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); if (pnode) { assert((*pnode)->ref > 0); (*pnode)->ref++; return (*pnode)->fd; } node = calloc(1, sizeof(*node)); if (!node) { log_errno(LOG_ERR, "calloc"); goto fail; } node->key = key; node->ref = 1; node->fd = socket(AF_INET, SOCK_DGRAM, 0); if (node->fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } if (0 != redudp_transparent(node->fd)) goto fail; if (0 != bind(node->fd, (struct sockaddr*)addr, sizeof(*addr))) { log_errno(LOG_ERR, "bind"); goto fail; } pnode = tsearch(node, &root_bound_udp4, bound_udp4_cmp); if (!pnode) { log_errno(LOG_ERR, "tsearch(%p) == %p", node, pnode); goto fail; } assert(node == *pnode); return node->fd; fail: if (node) { if (node->fd != -1) redsocks_close(node->fd); free(node); } return -1; } static void bound_udp4_put(const struct sockaddr_in *addr) { struct bound_udp4_key key; struct bound_udp4 **pnode, *node; void *parent; bound_udp4_mkkey(&key, addr); pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); assert(pnode && (*pnode)->ref > 0); node = *pnode; node->ref--; if (node->ref) return; parent = tdelete(node, &root_bound_udp4, bound_udp4_cmp); assert(parent); redsocks_close(node->fd); // expanding `pnode` to avoid use after free free(node); } static int redudp_transparent(int fd) { int on = 1; int error = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); if (error) log_errno(LOG_ERR, "setsockopt(..., SOL_IP, IP_TRANSPARENT)"); return error; } static int do_tproxy(redudp_instance* instance) { return instance->config.destaddr.sin_addr.s_addr == 0; } static struct sockaddr_in* get_destaddr(redudp_client *client) { if (do_tproxy(client->instance)) return &client->destaddr; else return &client->instance->config.destaddr; } static void redudp_fill_preamble(socks5_udp_preabmle *preamble, redudp_client *client) { preamble->reserved = 0; preamble->frag_no = 0; /* fragmentation is not supported */ preamble->addrtype = socks5_addrtype_ipv4; preamble->ip.addr = get_destaddr(client)->sin_addr.s_addr; preamble->ip.port = get_destaddr(client)->sin_port; } static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p) { int *do_password = p; return socks5_mkmethods_plain(*do_password); } static struct evbuffer* socks5_mkpassword_plain_wrapper(void *p) { redudp_instance *self = p; return socks5_mkpassword_plain(self->config.login, self->config.password); } static struct evbuffer* socks5_mkassociate(void *p) { struct sockaddr_in sa; p = p; /* Make compiler happy */ memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; return socks5_mkcommand_plain(socks5_cmd_udp_associate, &sa); } /*********************************************************************** * Logic */ static void redudp_drop_client(redudp_client *client) { redudp_log_error(client, LOG_INFO, "Dropping..."); enqueued_packet *q, *tmp; if (event_initialized(&client->timeout)) { if (event_del(&client->timeout) == -1) redudp_log_errno(client, LOG_ERR, "event_del"); } if (client->relay) redsocks_bufferevent_free(client->relay); if (event_initialized(&client->udprelay)) { int fd = event_get_fd(&client->udprelay); if (event_del(&client->udprelay) == -1) redudp_log_errno(client, LOG_ERR, "event_del"); redsocks_close(fd); } if (client->sender_fd != -1) bound_udp4_put(&client->destaddr); list_for_each_entry_safe(q, tmp, &client->queue, list) { list_del(&q->list); free(q); } list_del(&client->list); free(client); } static void redudp_bump_timeout(redudp_client *client) { struct timeval tv; tv.tv_sec = client->instance->config.udp_timeout; tv.tv_usec = 0; // TODO: implement udp_timeout_stream if (event_add(&client->timeout, &tv) != 0) { redudp_log_error(client, LOG_WARNING, "event_add(&client->timeout, ...)"); redudp_drop_client(client); } } static void redudp_forward_pkt(redudp_client *client, char *buf, size_t pktlen) { socks5_udp_preabmle req; struct msghdr msg; struct iovec io[2]; ssize_t outgoing, fwdlen = pktlen + sizeof(req); redudp_fill_preamble(&req, client); memset(&msg, 0, sizeof(msg)); msg.msg_name = &client->udprelayaddr; msg.msg_namelen = sizeof(client->udprelayaddr); msg.msg_iov = io; msg.msg_iovlen = SIZEOF_ARRAY(io); io[0].iov_base = &req; io[0].iov_len = sizeof(req); io[1].iov_base = buf; io[1].iov_len = pktlen; outgoing = sendmsg(event_get_fd(&client->udprelay), &msg, 0); if (outgoing == -1) { redudp_log_errno(client, LOG_WARNING, "sendmsg: Can't forward packet, dropping it"); return; } else if (outgoing != fwdlen) { redudp_log_error(client, LOG_WARNING, "sendmsg: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); return; } } static int redudp_enqeue_pkt(redudp_client *client, char *buf, size_t pktlen) { enqueued_packet *q = NULL; redudp_log_error(client, LOG_DEBUG, ""); if (client->queue_len >= client->instance->config.max_pktqueue) { redudp_log_error(client, LOG_WARNING, "There are already %u packets in queue. Dropping.", client->queue_len); return -1; } q = calloc(1, sizeof(enqueued_packet) + pktlen); if (!q) { redudp_log_errno(client, LOG_ERR, "Can't enqueue packet: calloc"); return -1; } q->len = pktlen; memcpy(q->data, buf, pktlen); client->queue_len += 1; list_add_tail(&q->list, &client->queue); return 0; } static void redudp_flush_queue(redudp_client *client) { enqueued_packet *q, *tmp; redudp_log_error(client, LOG_INFO, "Starting UDP relay"); list_for_each_entry_safe(q, tmp, &client->queue, list) { redudp_forward_pkt(client, q->data, q->len); list_del(&q->list); free(q); } client->queue_len = 0; assert(list_empty(&client->queue)); } static void redudp_read_assoc_reply(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; redudp_expected_assoc_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); int fd = -1; int error; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } if (reply.h.ver != socks5_ver) { redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected reply version: %u", reply.h.ver); goto fail; } if (reply.h.status != socks5_status_succeeded) { redudp_log_error(client, LOG_NOTICE, "Socks5 server status: \"%s\" (%i)", socks5_status_to_str(reply.h.status), reply.h.status); goto fail; } if (reply.h.addrtype != socks5_addrtype_ipv4) { redudp_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected address type for UDP dgram destination: %u", reply.h.addrtype); goto fail; } client->udprelayaddr.sin_family = AF_INET; client->udprelayaddr.sin_port = reply.ip.port; client->udprelayaddr.sin_addr.s_addr = reply.ip.addr; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { redudp_log_errno(client, LOG_ERR, "socket"); goto fail; } error = connect(fd, (struct sockaddr*)&client->udprelayaddr, sizeof(client->udprelayaddr)); if (error) { redudp_log_errno(client, LOG_NOTICE, "connect"); goto fail; } event_set(&client->udprelay, fd, EV_READ | EV_PERSIST, redudp_pkt_from_socks, client); error = event_add(&client->udprelay, NULL); if (error) { redudp_log_errno(client, LOG_ERR, "event_add"); goto fail; } redudp_flush_queue(client); // TODO: bufferevent_disable ? return; fail: if (fd != -1) redsocks_close(fd); redudp_drop_client(client); } static void redudp_read_auth_reply(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; socks5_auth_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); int error; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } if (reply.ver != socks5_password_ver || reply.status != socks5_password_passed) { redudp_log_error(client, LOG_NOTICE, "Socks5 authentication error. Version: %u, error code: %u", reply.ver, reply.status); goto fail; } error = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); if (error) goto fail; client->relay->readcb = redudp_read_assoc_reply; return; fail: redudp_drop_client(client); } static void redudp_read_auth_methods(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); socks5_method_reply reply; int read = evbuffer_remove(buffev->input, &reply, sizeof(reply)); const char *error = NULL; int ierror = 0; redudp_log_error(client, LOG_DEBUG, ""); if (read != sizeof(reply)) { redudp_log_errno(client, LOG_NOTICE, "evbuffer_remove returned only %i bytes instead of expected %zu", read, sizeof(reply)); goto fail; } error = socks5_is_known_auth_method(&reply, do_password); if (error) { redudp_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error); goto fail; } else if (reply.method == socks5_auth_none) { ierror = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkassociate, NULL, 0, /* last two are ignored */ sizeof(redudp_expected_assoc_reply), sizeof(redudp_expected_assoc_reply)); if (ierror) goto fail; client->relay->readcb = redudp_read_assoc_reply; } else if (reply.method == socks5_auth_password) { ierror = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkpassword_plain_wrapper, client->instance, 0, /* last one is ignored */ sizeof(socks5_auth_reply), sizeof(socks5_auth_reply)); if (ierror) goto fail; client->relay->readcb = redudp_read_auth_reply; } return; fail: redudp_drop_client(client); } static void redudp_relay_connected(struct bufferevent *buffev, void *_arg) { redudp_client *client = _arg; int do_password = socks5_is_valid_cred(client->instance->config.login, client->instance->config.password); int error; char relayaddr_str[RED_INET_ADDRSTRLEN]; redudp_log_error(client, LOG_DEBUG, "via %s", red_inet_ntop(&client->instance->config.relayaddr, relayaddr_str, sizeof(relayaddr_str))); if (!red_is_socket_connected_ok(buffev)) { redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); goto fail; } error = redsocks_write_helper_ex_plain( client->relay, NULL, socks5_mkmethods_plain_wrapper, &do_password, 0 /* does not matter */, sizeof(socks5_method_reply), sizeof(socks5_method_reply)); if (error) goto fail; client->relay->readcb = redudp_read_auth_methods; client->relay->writecb = 0; //bufferevent_disable(buffev, EV_WRITE); // I don't want to check for writeability. return; fail: redudp_drop_client(client); } static void redudp_relay_error(struct bufferevent *buffev, short what, void *_arg) { redudp_client *client = _arg; // TODO: FIXME: Implement me redudp_log_error(client, LOG_NOTICE, "redudp_relay_error"); redudp_drop_client(client); } static void redudp_timeout(int fd, short what, void *_arg) { redudp_client *client = _arg; redudp_log_error(client, LOG_INFO, "Client timeout. First: %li, last_client: %li, last_relay: %li.", client->first_event, client->last_client_event, client->last_relay_event); redudp_drop_client(client); } static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, struct sockaddr_in *destaddr, char *buf, size_t pktlen) { redudp_client *client = calloc(1, sizeof(*client)); if (!client) { log_errno(LOG_WARNING, "calloc"); return; } INIT_LIST_HEAD(&client->list); INIT_LIST_HEAD(&client->queue); client->instance = self; memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr)); if (destaddr) memcpy(&client->destaddr, destaddr, sizeof(client->destaddr)); evtimer_set(&client->timeout, redudp_timeout, client); // XXX: self->relay_ss->init(client); client->sender_fd = -1; // it's postponed until socks-server replies to avoid trivial DoS client->relay = red_connect_relay(&client->instance->config.relayaddr, redudp_relay_connected, redudp_relay_error, client); if (!client->relay) goto fail; if (redsocks_time(&client->first_event) == (time_t)-1) goto fail; client->last_client_event = client->first_event; redudp_bump_timeout(client); if (redudp_enqeue_pkt(client, buf, pktlen) == -1) goto fail; list_add(&client->list, &self->clients); redudp_log_error(client, LOG_INFO, "got 1st packet from client"); return; fail: redudp_drop_client(client); } static void redudp_pkt_from_socks(int fd, short what, void *_arg) { redudp_client *client = _arg; union { char buf[0xFFFF]; socks5_udp_preabmle header; } pkt; ssize_t pktlen, fwdlen, outgoing; struct sockaddr_in udprelayaddr; assert(fd == event_get_fd(&client->udprelay)); pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr, NULL); if (pktlen == -1) return; if (memcmp(&udprelayaddr, &client->udprelayaddr, sizeof(udprelayaddr)) != 0) { char buf[RED_INET_ADDRSTRLEN]; redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s.", red_inet_ntop(&udprelayaddr, buf, sizeof(buf))); return; } if (pkt.header.frag_no != 0) { // FIXME: does anybody need it? redudp_log_error(client, LOG_WARNING, "Got fragment #%u. Packet fragmentation is not supported!", pkt.header.frag_no); return; } if (pkt.header.addrtype != socks5_addrtype_ipv4) { redudp_log_error(client, LOG_NOTICE, "Got address type #%u instead of expected #%u (IPv4).", pkt.header.addrtype, socks5_addrtype_ipv4); return; } if (pkt.header.ip.port != get_destaddr(client)->sin_port || pkt.header.ip.addr != get_destaddr(client)->sin_addr.s_addr) { char buf[RED_INET_ADDRSTRLEN]; struct sockaddr_in pktaddr = { .sin_family = AF_INET, .sin_addr = { pkt.header.ip.addr }, .sin_port = pkt.header.ip.port, }; redudp_log_error(client, LOG_NOTICE, "Socks5 server relayed packet from unexpected address %s.", red_inet_ntop(&pktaddr, buf, sizeof(buf))); return; } redsocks_time(&client->last_relay_event); redudp_bump_timeout(client); if (do_tproxy(client->instance) && client->sender_fd == -1) { client->sender_fd = bound_udp4_get(&client->destaddr); if (client->sender_fd == -1) { redudp_log_error(client, LOG_WARNING, "bound_udp4_get failure"); return; } } fwdlen = pktlen - sizeof(pkt.header); outgoing = sendto(do_tproxy(client->instance) ? client->sender_fd : event_get_fd(&client->instance->listener), pkt.buf + sizeof(pkt.header), fwdlen, 0, (struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr)); if (outgoing != fwdlen) { redudp_log_error(client, LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", fwdlen, outgoing); return; } } static void redudp_pkt_from_client(int fd, short what, void *_arg) { redudp_instance *self = _arg; struct sockaddr_in clientaddr, destaddr, *pdestaddr; char buf[0xFFFF]; // UDP packet can't be larger then that ssize_t pktlen; redudp_client *tmp, *client = NULL; pdestaddr = do_tproxy(self) ? &destaddr : NULL; assert(fd == event_get_fd(&self->listener)); pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr, pdestaddr); if (pktlen == -1) return; // TODO: this lookup may be SLOOOOOW. list_for_each_entry(tmp, &self->clients, list) { // TODO: check destaddr if (0 == memcmp(&clientaddr, &tmp->clientaddr, sizeof(clientaddr))) { client = tmp; break; } } if (client) { redsocks_time(&client->last_client_event); redudp_bump_timeout(client); if (event_initialized(&client->udprelay)) { redudp_forward_pkt(client, buf, pktlen); } else { redudp_enqeue_pkt(client, buf, pktlen); } } else { redudp_first_pkt_from_client(self, &clientaddr, pdestaddr, buf, pktlen); } } /*********************************************************************** * Init / shutdown */ static parser_entry redudp_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 = "login", .type = pt_pchar }, { .key = "password", .type = pt_pchar }, { .key = "dest_ip", .type = pt_in_addr }, { .key = "dest_port", .type = pt_uint16 }, { .key = "udp_timeout", .type = pt_uint16 }, { .key = "udp_timeout_stream", .type = pt_uint16 }, { } }; static list_head instances = LIST_HEAD_INIT(instances); static int redudp_onenter(parser_section *section) { redudp_instance *instance = calloc(1, sizeof(*instance)); 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); instance->config.destaddr.sin_family = AF_INET; instance->config.max_pktqueue = 5; instance->config.udp_timeout = 30; instance->config.udp_timeout_stream = 180; 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, "login") == 0) ? (void*)&instance->config.login : (strcmp(entry->key, "password") == 0) ? (void*)&instance->config.password : (strcmp(entry->key, "dest_ip") == 0) ? (void*)&instance->config.destaddr.sin_addr : (strcmp(entry->key, "dest_port") == 0) ? (void*)&instance->config.destaddr.sin_port : (strcmp(entry->key, "max_pktqueue") == 0) ? (void*)&instance->config.max_pktqueue : (strcmp(entry->key, "udp_timeout") == 0) ? (void*)&instance->config.udp_timeout: (strcmp(entry->key, "udp_timeout_stream") == 0) ? (void*)&instance->config.udp_timeout_stream : NULL; section->data = instance; return 0; } static int redudp_onexit(parser_section *section) { redudp_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); instance->config.destaddr.sin_port = htons(instance->config.destaddr.sin_port); if (instance->config.udp_timeout_stream < instance->config.udp_timeout) { parser_error(section->context, "udp_timeout_stream should be not less then udp_timeout"); return -1; } list_add(&instance->list, &instances); return 0; } static int redudp_init_instance(redudp_instance *instance) { /* FIXME: redudp_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 = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } if (do_tproxy(instance)) { int on = 1; char buf[RED_INET_ADDRSTRLEN]; // iptables TPROXY target does not send packets to non-transparent sockets if (0 != redudp_transparent(fd)) goto fail; error = setsockopt(fd, SOL_IP, IP_RECVORIGDSTADDR, &on, sizeof(on)); if (error) { log_errno(LOG_ERR, "setsockopt(listener, SOL_IP, IP_RECVORIGDSTADDR)"); goto fail; } log_error(LOG_DEBUG, "redudp @ %s: TPROXY", red_inet_ntop(&instance->config.bindaddr, buf, sizeof(buf))); } else { char buf1[RED_INET_ADDRSTRLEN], buf2[RED_INET_ADDRSTRLEN]; log_error(LOG_DEBUG, "redudp @ %s: destaddr=%s", red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), red_inet_ntop(&instance->config.destaddr, buf2, sizeof(buf2))); } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } event_set(&instance->listener, fd, EV_READ | EV_PERSIST, redudp_pkt_from_client, instance); error = event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } return 0; fail: redudp_fini_instance(instance); if (fd != -1) { redsocks_close(fd); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void redudp_fini_instance(redudp_instance *instance) { if (!list_empty(&instance->clients)) { redudp_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) { redudp_drop_client(client); } } 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.login); free(instance->config.password); memset(instance, 0, sizeof(*instance)); free(instance); } static int redudp_init() { redudp_instance *tmp, *instance = NULL; // TODO: init debug_dumper list_for_each_entry_safe(instance, tmp, &instances, list) { if (redudp_init_instance(instance) != 0) goto fail; } return 0; fail: redudp_fini(); return -1; } static int redudp_fini() { redudp_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) redudp_fini_instance(instance); return 0; } static parser_section redudp_conf_section = { .name = "redudp", .entries = redudp_entries, .onenter = redudp_onenter, .onexit = redudp_onexit }; app_subsys redudp_subsys = { .init = redudp_init, .fini = redudp_fini, .conf_section = &redudp_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/base.h0000644000175000017500000000076113030520742015570 0ustar apoikosapoikos#ifndef BASE_H_SUN_JUN__3_20_15_57_2007 #define BASE_H_SUN_JUN__3_20_15_57_2007 int getdestaddr(int fd, const struct sockaddr_in *client, const struct sockaddr_in *bindaddr, struct sockaddr_in *destaddr); int apply_tcp_keepalive(int fd); uint32_t redsocks_conn_max(); uint32_t connpres_idle_timeout(); uint32_t max_accept_backoff_ms(); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* BASE_H_SUN_JUN__3_20_15_57_2007 */ redsocks-release-0.5/socks5.h0000644000175000017500000000565413030520742016073 0ustar apoikosapoikos#ifndef SOCKS5_H #define SOCKS5_H #include #include "utils.h" typedef struct socks5_method_req_t { uint8_t ver; uint8_t num_methods; uint8_t methods[1]; // at least one } PACKED socks5_method_req; typedef struct socks5_method_reply_t { uint8_t ver; uint8_t method; } PACKED socks5_method_reply; static const int socks5_ver = 5; static const int socks5_auth_none = 0x00; static const int socks5_auth_gssapi = 0x01; static const int socks5_auth_password = 0x02; static const int socks5_auth_invalid = 0xFF; typedef struct socks5_auth_reply_t { uint8_t ver; uint8_t status; } PACKED socks5_auth_reply; static const int socks5_password_ver = 0x01; static const int socks5_password_passed = 0x00; typedef struct socks5_addr_ipv4_t { uint32_t addr; uint16_t port; } PACKED socks5_addr_ipv4; typedef struct socks5_addr_domain_t { uint8_t size; uint8_t more[1]; /* uint16_t port; */ } PACKED socks5_addr_domain; typedef struct socks5_addr_ipv6_t { uint8_t addr[16]; uint16_t port; } PACKED socks5_addr_ipv6; typedef struct socks5_req_t { uint8_t ver; uint8_t cmd; uint8_t reserved; uint8_t addrtype; /* socks5_addr_* */ } PACKED socks5_req; typedef struct socks5_reply_t { uint8_t ver; uint8_t status; uint8_t reserved; uint8_t addrtype; /* socks5_addr_* */ } PACKED socks5_reply; typedef struct socks5_udp_preabmle_t { uint16_t reserved; uint8_t frag_no; uint8_t addrtype; /* 0x01 for IPv4 */ /* socks5_addr_* */ socks5_addr_ipv4 ip; /* I support only IPv4 at the moment */ } PACKED socks5_udp_preabmle; static const int socks5_reply_maxlen = 512; // as domain name can't be longer than 256 bytes static const int socks5_addrtype_ipv4 = 1; static const int socks5_addrtype_domain = 3; static const int socks5_addrtype_ipv6 = 4; static const int socks5_status_succeeded = 0; static const int socks5_status_server_failure = 1; static const int socks5_status_connection_not_allowed_by_ruleset = 2; static const int socks5_status_Network_unreachable = 3; static const int socks5_status_Host_unreachable = 4; static const int socks5_status_Connection_refused = 5; static const int socks5_status_TTL_expired = 6; static const int socks5_status_Command_not_supported = 7; static const int socks5_status_Address_type_not_supported = 8; const char* socks5_status_to_str(int socks5_status); bool socks5_is_valid_cred(const char *login, const char *password); struct evbuffer *socks5_mkmethods_plain(int do_password); struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password); const char* socks5_is_known_auth_method(socks5_method_reply *reply, int do_password); static const int socks5_cmd_connect = 1; static const int socks5_cmd_bind = 2; static const int socks5_cmd_udp_associate = 3; struct evbuffer *socks5_mkcommand_plain(int socks5_cmd, const struct sockaddr_in *destaddr); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* SOCKS5_H */ redsocks-release-0.5/base64.h0000644000175000017500000000345613030520742015746 0ustar apoikosapoikos/* * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) * * This file is part of FFmpeg. * * FFmpeg 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. * * FFmpeg 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 FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef BASE64_H #define BASE64_H #include /** * Decode a base64-encoded string. * * @param out buffer for decoded data * @param in null-terminated input string * @param out_size size in bytes of the out buffer, must be at * least 3/4 of the length of in * @return number of bytes written, or a negative value in case of * invalid input */ int base64_decode(uint8_t *out, const char *in, int out_size); /** * Encode data to base64 and null-terminate. * * @param out buffer for encoded data * @param out_size size in bytes of the output buffer, must be at * least BASE64_SIZE(in_size) * @param in_size size in bytes of the 'in' buffer * @return 'out' or NULL in case of error */ char *base64_encode(char *out, int out_size, const uint8_t *in, int in_size); /** * Calculate the output size needed to base64-encode x bytes. */ #define BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) #endif /* BASE64_H */ redsocks-release-0.5/main.h0000644000175000017500000000122713030520742015600 0ustar apoikosapoikos#ifndef MAIN_H_TUE_JAN_23_15_38_25_2007 #define MAIN_H_TUE_JAN_23_15_38_25_2007 #include "parser.h" struct event_base; typedef struct app_subsys_t { int (*init)(struct event_base*); int (*fini)(); parser_section* conf_section; } app_subsys; #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* MAIN_H_TUE_JAN_23_15_38_25_2007 */ redsocks-release-0.5/.gitignore0000644000175000017500000000004313030520742016466 0ustar apoikosapoikos*.o config.h tags redsocks .depend redsocks-release-0.5/main.c0000644000175000017500000001140413030520742015571 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "log.h" #include "main.h" #include "utils.h" #include "version.h" #include "config.h" #include "base.h" extern app_subsys redsocks_subsys; extern app_subsys debug_subsys; extern app_subsys base_subsys; extern app_subsys redudp_subsys; extern app_subsys dnstc_subsys; app_subsys *subsystems[] = { &redsocks_subsys, #ifdef DBG_BUILD &debug_subsys, #endif &base_subsys, &redudp_subsys, &dnstc_subsys, }; static const char *confname = "redsocks.conf"; static const char *pidfile = NULL; static void terminate(int sig, short what, void *_arg) { if (event_loopbreak() != 0) log_error(LOG_WARNING, "event_loopbreak"); } int main(int argc, char **argv) { int error; app_subsys **ss; int exit_signals[2] = {SIGTERM, SIGINT}; struct event terminators[2]; bool conftest = false; int opt; int i; evutil_secure_rng_init(); while ((opt = getopt(argc, argv, "h?vtc:p:")) != -1) { switch (opt) { case 't': conftest = true; break; case 'c': confname = optarg; break; case 'p': pidfile = optarg; break; case 'v': puts(redsocks_version); printf("Built with libevent-%s\n", LIBEVENT_VERSION); printf("Runs with libevent-%s\n", event_get_version()); if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { printf("Warning: libevent version number mismatch.\n" " Headers: %8x\n" " Runtime: %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); } return EXIT_SUCCESS; default: printf( "Usage: %s [-?hvt] [-c config] [-p pidfile]\n" " -h, -? this message\n" " -v print version\n" " -t test config syntax\n" " -p write pid to pidfile\n", argv[0]); return (opt == '?' || opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; } } if (event_get_struct_event_size() != sizeof(struct event)) { puts("libevent event_get_struct_event_size() != sizeof(struct event)! Check `redsocks -v` and recompile redsocks"); return EXIT_FAILURE; } FILE *f = fopen(confname, "r"); if (!f) { perror("Unable to open config file"); return EXIT_FAILURE; } parser_context* parser = parser_start(f); if (!parser) { perror("Not enough memory for parser"); return EXIT_FAILURE; } FOREACH(ss, subsystems) if ((*ss)->conf_section) parser_add_section(parser, (*ss)->conf_section); error = parser_run(parser); parser_stop(parser); fclose(f); if (error) return EXIT_FAILURE; if (conftest) return EXIT_SUCCESS; struct event_base* evbase = event_init(); memset(terminators, 0, sizeof(terminators)); FOREACH(ss, subsystems) { if ((*ss)->init) { error = (*ss)->init(evbase); if (error) goto shutdown; } } if (pidfile) { f = fopen(pidfile, "w"); if (!f) { perror("Unable to open pidfile for write"); return EXIT_FAILURE; } fprintf(f, "%d\n", getpid()); fclose(f); } assert(SIZEOF_ARRAY(exit_signals) == SIZEOF_ARRAY(terminators)); for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { signal_set(&terminators[i], exit_signals[i], terminate, NULL); if (signal_add(&terminators[i], NULL) != 0) { log_errno(LOG_ERR, "signal_add"); goto shutdown; } } if (LIBEVENT_VERSION_NUMBER != event_get_version_number()) { log_error(LOG_WARNING, "libevent version mismatch! headers %8x, runtime %8x\n", LIBEVENT_VERSION_NUMBER, event_get_version_number()); } log_error(LOG_NOTICE, "redsocks started, conn_max=%u", redsocks_conn_max()); event_dispatch(); log_error(LOG_NOTICE, "redsocks goes down"); shutdown: for (i = 0; i < SIZEOF_ARRAY(exit_signals); i++) { if (signal_initialized(&terminators[i])) { if (signal_del(&terminators[i]) != 0) log_errno(LOG_WARNING, "signal_del"); memset(&terminators[i], 0, sizeof(terminators[i])); } } for (--ss; ss >= subsystems; ss--) if ((*ss)->fini) (*ss)->fini(); event_base_free(evbase); return !error ? EXIT_SUCCESS : EXIT_FAILURE; } /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/utils.h0000644000175000017500000000523513030520742016017 0ustar apoikosapoikos#ifndef UTILS_H_SAT_FEB__2_02_24_05_2008 #define UTILS_H_SAT_FEB__2_02_24_05_2008 #include #include #include struct sockaddr_in; #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) #define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++) #define FOREACH_REV(ptr, array) for (ptr = array + SIZEOF_ARRAY(array) - 1; ptr >= array; ptr--) #define UNUSED(x) ((void)(x)) #if defined __GNUC__ #define PACKED __attribute__((packed)) #else #error Unknown compiler, modify utils.h for it #endif #ifdef __GNUC__ #define member_type(type, member) __typeof(((type *)0)->member) #else #define member_type(type, member) const void #endif /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) \ ((type *)( \ (char *)(member_type(type, member) *){ ptr } - offsetof(type, member) \ )) #define clamp_value(value, min_val, max_val) do { \ if (value < min_val) \ value = min_val; \ if (value > max_val) \ value = max_val; \ } while (0) uint32_t red_randui32(); time_t redsocks_time(time_t *t); int redsocks_gettimeofday(struct timeval *tv); char *redsocks_evbuffer_readline(struct evbuffer *buf); struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); int red_socket_geterrno(struct bufferevent *buffev); int red_is_socket_connected_ok(struct bufferevent *buffev); int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *fromaddr, struct sockaddr_in *toaddr); int fcntl_nonblock(int fd); #define event_fmt_str "%s|%s|%s|%s|%s|0x%x" #define event_fmt(what) \ (what) & EVBUFFER_READ ? "EVBUFFER_READ" : "0", \ (what) & EVBUFFER_WRITE ? "EVBUFFER_WRITE" : "0", \ (what) & EVBUFFER_EOF ? "EVBUFFER_EOF" : "0", \ (what) & EVBUFFER_ERROR ? "EVBUFFER_ERROR" : "0", \ (what) & EVBUFFER_TIMEOUT ? "EVBUFFER_TIMEOUT" : "0", \ (what) & ~(EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF|EVBUFFER_ERROR|EVBUFFER_TIMEOUT) #if INET6_ADDRSTRLEN < INET_ADDRSTRLEN # error Impossible happens: INET6_ADDRSTRLEN < INET_ADDRSTRLEN #else # define RED_INET_ADDRSTRLEN (INET6_ADDRSTRLEN + 1 + 5 + 1) // addr + : + port + \0 #endif char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */ redsocks-release-0.5/redsocks.service0000644000175000017500000000066613030520742017710 0ustar apoikosapoikos[Unit] Description=Transparent redirector of any TCP connection to proxy using your firewall [Service] Type=forking PIDFile=/run/redsocks/redsocks.pid EnvironmentFile=/etc/conf.d/redsocks User=redsocks ExecStartPre=/usr/bin/redsocks -t -c $REDSOCKS_CONF ExecStart=/usr/bin/redsocks -c $REDSOCKS_CONF \ -p /run/redsocks/redsocks.pid ExecStopPost=/bin/rm /run/redsocks/redsocks.pid Restart=on-abort [Install] WantedBy=multi-user.target redsocks-release-0.5/dnstc.c0000644000175000017500000001464613030520742015773 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "list.h" #include "log.h" #include "parser.h" #include "main.h" #include "redsocks.h" #include "dnstc.h" #include "utils.h" #define dnstc_log_error(prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &clientaddr, &self->config.bindaddr, prio, ## msg) #define dnstc_log_errno(prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &clientaddr, &self->config.bindaddr, prio, ## msg) static void dnstc_fini_instance(dnstc_instance *instance); static int dnstc_fini(); typedef struct dns_header_t { uint16_t id; uint8_t qr_opcode_aa_tc_rd; uint8_t ra_z_rcode; uint16_t qdcount; uint16_t ancount; uint16_t nscount; uint16_t arcount; } PACKED dns_header; #define DNS_QR 0x80 #define DNS_TC 0x02 #define DNS_Z 0x70 /*********************************************************************** * Logic */ static void dnstc_pkt_from_client(int fd, short what, void *_arg) { dnstc_instance *self = _arg; struct sockaddr_in clientaddr; union { char raw[0xFFFF]; // UDP packet can't be larger then that dns_header h; } buf; ssize_t pktlen, outgoing; assert(fd == event_get_fd(&self->listener)); pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr, NULL); if (pktlen == -1) return; if (pktlen <= sizeof(dns_header)) { dnstc_log_error(LOG_INFO, "incomplete DNS request"); return; } if (1 && (buf.h.qr_opcode_aa_tc_rd & DNS_QR) == 0 /* query */ && (buf.h.ra_z_rcode & DNS_Z) == 0 /* Z is Zero */ && buf.h.qdcount /* some questions */ && !buf.h.ancount && !buf.h.nscount && !buf.h.arcount /* no answers */ ) { buf.h.qr_opcode_aa_tc_rd |= DNS_QR; buf.h.qr_opcode_aa_tc_rd |= DNS_TC; outgoing = sendto(fd, buf.raw, pktlen, 0, (struct sockaddr*)&clientaddr, sizeof(clientaddr)); if (outgoing != pktlen) dnstc_log_errno(LOG_WARNING, "sendto: I was sending %zd bytes, but only %zd were sent.", pktlen, outgoing); else dnstc_log_error(LOG_INFO, "sent truncated DNS reply"); } else { dnstc_log_error(LOG_INFO, "malformed DNS request"); } } /*********************************************************************** * Init / shutdown */ static parser_entry dnstc_entries[] = { { .key = "local_ip", .type = pt_in_addr }, { .key = "local_port", .type = pt_uint16 }, { } }; static list_head instances = LIST_HEAD_INIT(instances); static int dnstc_onenter(parser_section *section) { dnstc_instance *instance = calloc(1, sizeof(*instance)); if (!instance) { parser_error(section->context, "Not enough memory"); return -1; } INIT_LIST_HEAD(&instance->list); instance->config.bindaddr.sin_family = AF_INET; instance->config.bindaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 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 : NULL; section->data = instance; return 0; } static int dnstc_onexit(parser_section *section) { dnstc_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); list_add(&instance->list, &instances); return 0; } static int dnstc_init_instance(dnstc_instance *instance) { /* FIXME: dnstc_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 = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); goto fail; } event_set(&instance->listener, fd, EV_READ | EV_PERSIST, dnstc_pkt_from_client, instance); error = event_add(&instance->listener, NULL); if (error) { log_errno(LOG_ERR, "event_add"); goto fail; } return 0; fail: dnstc_fini_instance(instance); if (fd != -1) { if (close(fd) != 0) log_errno(LOG_WARNING, "close"); } return -1; } /* Drops instance completely, freeing its memory and removing from * instances list. */ static void dnstc_fini_instance(dnstc_instance *instance) { if (event_initialized(&instance->listener)) { if (event_del(&instance->listener) != 0) log_errno(LOG_WARNING, "event_del"); if (close(event_get_fd(&instance->listener)) != 0) log_errno(LOG_WARNING, "close"); memset(&instance->listener, 0, sizeof(instance->listener)); } list_del(&instance->list); memset(instance, 0, sizeof(*instance)); free(instance); } static int dnstc_init() { dnstc_instance *tmp, *instance = NULL; // TODO: init debug_dumper list_for_each_entry_safe(instance, tmp, &instances, list) { if (dnstc_init_instance(instance) != 0) goto fail; } return 0; fail: dnstc_fini(); return -1; } static int dnstc_fini() { dnstc_instance *tmp, *instance = NULL; list_for_each_entry_safe(instance, tmp, &instances, list) dnstc_fini_instance(instance); return 0; } static parser_section dnstc_conf_section = { .name = "dnstc", .entries = dnstc_entries, .onenter = dnstc_onenter, .onexit = dnstc_onexit }; app_subsys dnstc_subsys = { .init = dnstc_init, .fini = dnstc_fini, .conf_section = &dnstc_conf_section, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/version.h0000644000175000017500000000025213030520742016336 0ustar apoikosapoikos#ifndef VERSION_H_SUN_NOV_27_03_22_30_2011 #define VERSION_H_SUN_NOV_27_03_22_30_2011 extern const char* redsocks_version; #endif // VERSION_H_SUN_NOV_27_03_22_30_2011 redsocks-release-0.5/md5.h0000644000175000017500000000650013030520742015340 0ustar apoikosapoikos/* Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.h is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed references to Ghostscript; clarified derivation from RFC 1321; now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); added conditionalization for C++ compilation from Martin Purschke . 1999-05-03 lpd Original version. */ #ifndef md5_INCLUDED # define md5_INCLUDED /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is * defined as non-zero, the code will be compiled to run only on big-endian * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to * run on either big- or little-endian CPUs, but will run slightly less * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. */ typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; #ifdef __cplusplus extern "C" { #endif /* Initialize the algorithm. */ void md5_init(md5_state_t *pms); /* Append a string to the message. */ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); #ifdef __cplusplus } /* end extern "C" */ #endif #endif /* md5_INCLUDED */ redsocks-release-0.5/parser.h0000644000175000017500000000335513030520742016154 0ustar apoikosapoikos#ifndef PARSER_H_THU_JAN_11_04_49_38_2007 #define PARSER_H_THU_JAN_11_04_49_38_2007 #include #include enum disclose_src_e { DISCLOSE_NONE, DISCLOSE_X_FORWARDED_FOR, DISCLOSE_FORWARDED_IP, DISCLOSE_FORWARDED_IPPORT, }; enum on_proxy_fail_e { ONFAIL_CLOSE, ONFAIL_FORWARD_HTTP_ERR, }; typedef enum { pt_bool, // "bool" from stdbool.h, not "_Bool" or anything else pt_pchar, pt_uint16, pt_uint32, pt_in_addr, pt_in_addr2, // inaddr[0] = net, inaddr[1] = netmask pt_disclose_src, pt_on_proxy_fail, pt_obsolete, pt_redsocks_max_accept_backoff, } parser_type; typedef struct parser_entry_t { const char *key; parser_type type; void *addr; } parser_entry; typedef struct parser_context_t parser_context; typedef struct parser_section_t parser_section; typedef int (*parser_section_onenter)(parser_section *section); typedef int (*parser_section_onexit)(parser_section *section); struct parser_section_t { parser_section *next; parser_context *context; const char *name; parser_section_onenter onenter; // is called on entry to section parser_section_onexit onexit; // is called on exit from section parser_entry *entries; void *data; }; parser_context* parser_start(FILE *fd); void parser_add_section(parser_context *context, parser_section *section); int parser_run(parser_context *context); void parser_error(parser_context *context, const char *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 2, 3) )) #endif ; void parser_stop(parser_context *context); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* PARSER_H_THU_JAN_11_04_49_38_2007 */ redsocks-release-0.5/.travis.yml0000644000175000017500000000012613030520742016611 0ustar apoikosapoikosos: - linux - osx language: c compiler: - gcc - clang script: - make all redsocks-release-0.5/redsocks.h0000644000175000017500000001407613030520742016477 0ustar apoikosapoikos#ifndef REDSOCKS_H_WED_JAN_24_22_17_11_2007 #define REDSOCKS_H_WED_JAN_24_22_17_11_2007 #include #include #include #include #include #include "list.h" #include "parser.h" struct redsocks_client_t; struct redsocks_instance_t; typedef struct relay_subsys_t { char *name; size_t payload_len; // size of relay-specific data in client section size_t instance_payload_len; // size of relay-specify data in instance section evbuffercb readcb; evbuffercb writecb; void (*init)(struct redsocks_client_t *client); void (*fini)(struct redsocks_client_t *client); void (*instance_init)(struct redsocks_instance_t *instance); void (*instance_fini)(struct redsocks_instance_t *instance); // connect_relay (if any) is called instead of redsocks_connect_relay after client connection acceptance void (*connect_relay)(struct redsocks_client_t *client); } relay_subsys; typedef struct redsocks_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; char *type; char *login; char *password; uint16_t listenq; bool use_splice; enum disclose_src_e disclose_src; enum on_proxy_fail_e on_proxy_fail; } redsocks_config; typedef struct redsocks_instance_t { list_head list; redsocks_config config; struct event listener; list_head clients; relay_subsys *relay_ss; } redsocks_instance; typedef unsigned short evshut_t; // EV_READ | EV_WRITE typedef struct redsocks_client_t { list_head list; redsocks_instance *instance; struct bufferevent *client; struct bufferevent *relay; struct sockaddr_in clientaddr; struct sockaddr_in destaddr; int state; // it's used by bottom layer evshut_t client_evshut; evshut_t relay_evshut; struct timeval first_event; struct timeval last_event; } redsocks_client; typedef struct splice_pipe_t { int read; int write; size_t size; } splice_pipe; typedef struct redsocks_pump_t { /* Quick-n-dirty test show, that some Linux 4.4.0 build uses ~1.5 kb of * slab_unreclaimable RAM per every pipe pair. Most of connections are * usually idle and it's possble to save some measurable amount of RAM * using shared pipe pool. */ redsocks_client c; splice_pipe request; splice_pipe reply; struct event client_read; struct event client_write; struct event relay_read; struct event relay_write; } redsocks_pump; static inline size_t sizeof_client(redsocks_instance *i) { return ((i->config.use_splice) ? sizeof(redsocks_pump) : sizeof(redsocks_client)) + i->relay_ss->payload_len; } static inline void* red_payload(redsocks_client *c) { return (c->instance->config.use_splice) ? (void*)(((redsocks_pump*)c) + 1) : (void*)(c + 1); } static inline redsocks_pump* red_pump(redsocks_client *c) { assert(c->instance->config.use_splice); return (redsocks_pump*)c; } void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); void redsocks_drop_client(redsocks_client *client); void redsocks_touch_client(redsocks_client *client); void redsocks_connect_relay(redsocks_client *client); void redsocks_start_relay(redsocks_client *client); bool redsocks_has_splice_instance(); typedef int (*size_comparator)(size_t a, size_t b); int sizes_equal(size_t a, size_t b); int sizes_greater_equal(size_t a, size_t b); /** helper for functions when we expect ONLY reply of some size and anything else is error */ int redsocks_read_expected(redsocks_client *client, struct evbuffer *input, void *data, size_comparator comparator, size_t expected); typedef struct evbuffer* (*redsocks_message_maker)(redsocks_client *client); typedef struct evbuffer* (*redsocks_message_maker_plain)(void *p); struct evbuffer *mkevbuffer(void *data, size_t len); /* Yahoo! This code is ex-plain! :-D */ 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 redsocks_write_helper_ex( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high); int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only); #define redsocks_close(fd) redsocks_close_internal((fd), __FILE__, __LINE__, __func__) void redsocks_close_internal(int fd, const char* file, int line, const char *func); #define redsocks_event_add(client, ev) redsocks_event_add_internal((client), (ev), __FILE__, __LINE__, __func__) void redsocks_event_add_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func); #define redsocks_event_del(client, ev) redsocks_event_del_internal((client), (ev), __FILE__, __LINE__, __func__) void redsocks_event_del_internal(redsocks_client *client, struct event *ev, const char *file, int line, const char *func); #define redsocks_bufferevent_dropfd(client, ev) redsocks_bufferevent_dropfd_internal((client), (ev), __FILE__, __LINE__, __func__) void redsocks_bufferevent_dropfd_internal(redsocks_client *client, struct bufferevent *ev, const char *file, int line, const char *func); // I have to account descriptiors for accept-backoff, that's why BEV_OPT_CLOSE_ON_FREE is not used. void redsocks_bufferevent_free(struct bufferevent *buffev); #define redsocks_log_error(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) #define redsocks_log_errno(client, prio, msg...) \ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->destaddr, prio, ## msg) 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 *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 8, 9) )) #endif ; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDSOCKS_H_WED_JAN_24_22_17_11_2007 */ redsocks-release-0.5/socks4.c0000644000175000017500000000777713030520742016075 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 "log.h" #include "redsocks.h" typedef enum socks4_state_t { socks4_new, socks4_request_sent, socks4_reply_came, socks4_MAX, } socks4_state; typedef struct socks4_req_t { uint8_t ver; uint8_t cmd; uint16_t port; uint32_t addr; char login[1]; // we need at least zero-byte } PACKED socks4_req; const int socks4_ver = 4; const int socks4_cmd_connect = 1; const int socks4_cmd_bind = 2; typedef struct socks4_reply_t { uint8_t ver; uint8_t status; uint16_t port; uint32_t addr; } PACKED socks4_reply; const int socks4_status_ok = 90; const int socks4_status_fail = 91; const int socks4_status_no_ident = 92; const int socks4_status_fake_ident = 93; static void socks4_instance_init(redsocks_instance *instance) { if (instance->config.password) log_error(LOG_WARNING, "password <%s> is ignored for socks4 connections", instance->config.password); } static void socks4_client_init(redsocks_client *client) { client->state = socks4_new; } static void socks4_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; assert(client->state >= socks4_request_sent); redsocks_touch_client(client); if (client->state == socks4_request_sent) { socks4_reply reply; if (redsocks_read_expected(client, buffev->input, &reply, sizes_greater_equal, sizeof(reply)) < 0) return; client->state = socks4_reply_came; if (reply.ver != 0) { redsocks_log_error(client, LOG_NOTICE, "Socks4 server reported unexpected reply version..."); redsocks_drop_client(client); } else if (reply.status == socks4_status_ok) redsocks_start_relay(client); else { redsocks_log_error(client, LOG_NOTICE, "Socks4 server status: %s (%i)", reply.status == socks4_status_fail ? "fail" : reply.status == socks4_status_no_ident ? "no ident" : reply.status == socks4_status_fake_ident ? "fake ident" : "?", reply.status); redsocks_drop_client(client); } } } static struct evbuffer *socks4_mkconnect(redsocks_client *client) { const redsocks_config *config = &client->instance->config; const char *username = config->login ? config->login : ""; // space for \0 comes from socks4_req->login size_t username_len = strlen(username); size_t len = sizeof(socks4_req) + username_len; socks4_req *req = calloc(1, len); req->ver = socks4_ver; req->cmd = socks4_cmd_connect; req->port = client->destaddr.sin_port; req->addr = client->destaddr.sin_addr.s_addr; memcpy(req->login, username, username_len + 1); struct evbuffer *ret = mkevbuffer(req, len); free(req); return ret; } static void socks4_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; redsocks_touch_client(client); if (client->state == socks4_new) { redsocks_write_helper( buffev, client, socks4_mkconnect, socks4_request_sent, sizeof(socks4_reply) ); } else if (client->state >= socks4_request_sent) { bufferevent_disable(buffev, EV_WRITE); } } relay_subsys socks4_subsys = { .name = "socks4", .payload_len = 0, .instance_payload_len = 0, .readcb = socks4_read_cb, .writecb = socks4_write_cb, .init = socks4_client_init, .instance_init = socks4_instance_init, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/http-relay.c0000644000175000017500000003707713030520742016754 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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. * * * http-relay upstream module for redsocks */ #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "redsocks.h" #include "http-auth.h" #include "utils.h" #define HTTP_HEAD_WM_HIGH (4096) typedef enum httpr_state_t { httpr_new, httpr_recv_request_headers, httpr_request_sent, httpr_reply_came, httpr_headers_skipped, httpr_MAX, } httpr_state; typedef struct httpr_buffer_t { char *buff; int len; int max_len; } httpr_buffer; typedef struct httpr_client_t { char *firstline; char *host; int has_host; httpr_buffer client_buffer; httpr_buffer relay_buffer; } httpr_client; extern const char *auth_request_header; extern const char *auth_response_header; static void httpr_connect_relay(redsocks_client *client); static int httpr_buffer_init(httpr_buffer *buff) { buff->max_len = 4096; buff->len = 0; buff->buff = calloc(buff->max_len, 1); if (!buff->buff) return -1; return 0; } static void httpr_buffer_fini(httpr_buffer *buff) { free(buff->buff); buff->buff = NULL; } static int httpr_buffer_append(httpr_buffer *buff, const char *data, int len) { while (buff->len + len + 1 > buff->max_len) { /* double the buffer size */ buff->max_len *= 2; } char *new_buff = calloc(buff->max_len, 1); if (!new_buff) { return -1; } memcpy(new_buff, buff->buff, buff->len); memcpy(new_buff + buff->len, data, len); buff->len += len; new_buff[buff->len] = '\0'; free(buff->buff); buff->buff = new_buff; return 0; } static void httpr_client_init(redsocks_client *client) { httpr_client *httpr = red_payload(client); client->state = httpr_new; memset(httpr, 0, sizeof(*httpr)); httpr_buffer_init(&httpr->client_buffer); httpr_buffer_init(&httpr->relay_buffer); } static void httpr_client_fini(redsocks_client *client) { httpr_client *httpr = red_payload(client); free(httpr->firstline); httpr->firstline = NULL; free(httpr->host); httpr->host = NULL; httpr_buffer_fini(&httpr->client_buffer); httpr_buffer_fini(&httpr->relay_buffer); } static void httpr_instance_init(redsocks_instance *instance) { log_error(LOG_WARNING, "You should avoid `http-relay`, e.g. due to CVE-2009-0801"); } static void httpr_instance_fini(redsocks_instance *instance) { http_auth *auth = red_http_auth(instance); free(auth->last_auth_query); auth->last_auth_query = NULL; } static void httpr_relay_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = red_payload(client); int dropped = 0; assert(client->state >= httpr_request_sent); redsocks_touch_client(client); httpr_buffer_fini(&httpr->relay_buffer); httpr_buffer_init(&httpr->relay_buffer); if (client->state == httpr_request_sent) { size_t len = evbuffer_get_length(buffev->input); char *line = redsocks_evbuffer_readline(buffev->input); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); unsigned int code; if (sscanf(line, "HTTP/%*u.%*u %u", &code) == 1) { // 1 == one _assigned_ match if (code == 407) { // auth failed http_auth *auth = red_http_auth(client->instance); if (auth->last_auth_query != NULL && auth->last_auth_count == 1) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth failed: %s", line); redsocks_drop_client(client); dropped = 1; } else if (client->instance->config.login == NULL || client->instance->config.password == NULL) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no login/password configured: %s", line); redsocks_drop_client(client); dropped = 1; } else { free(line); char *auth_request = http_auth_request_header(buffev->input, NULL); if (!auth_request) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy auth required, but no <%s> header found: %s", auth_request_header, line); redsocks_drop_client(client); dropped = 1; } else { free(auth->last_auth_query); char *ptr = auth_request; ptr += strlen(auth_request_header); while (isspace(*ptr)) ptr++; auth->last_auth_query = calloc(strlen(ptr) + 1, 1); strcpy(auth->last_auth_query, ptr); auth->last_auth_count = 0; free(auth_request); httpr_buffer_fini(&httpr->relay_buffer); if (bufferevent_disable(client->relay, EV_WRITE)) { redsocks_log_errno(client, LOG_ERR, "bufferevent_disable"); return; } /* close relay tunnel */ redsocks_bufferevent_free(client->relay); /* set to initial state*/ client->state = httpr_recv_request_headers; /* and reconnect */ redsocks_connect_relay(client); return; } } } else if (100 <= code && code <= 999) { client->state = httpr_reply_came; } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy error: %s", line); redsocks_drop_client(client); dropped = 1; } } else { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy bad firstline: %s", line); redsocks_drop_client(client); dropped = 1; } free(line); } else if (len >= HTTP_HEAD_WM_HIGH) { redsocks_log_error(client, LOG_NOTICE, "HTTP Proxy reply is too long, %zu bytes", len); redsocks_drop_client(client); dropped = 1; } } if (dropped) return; while (client->state == httpr_reply_came) { char *line = redsocks_evbuffer_readline(buffev->input); if (line) { httpr_buffer_append(&httpr->relay_buffer, line, strlen(line)); httpr_buffer_append(&httpr->relay_buffer, "\r\n", 2); if (strlen(line) == 0) { client->state = httpr_headers_skipped; } free(line); } else { break; } } if (client->state == httpr_headers_skipped) { if (bufferevent_write(client->client, httpr->relay_buffer.buff, httpr->relay_buffer.len) != 0) { redsocks_log_error(client, LOG_NOTICE, "bufferevent_write"); redsocks_drop_client(client); return; } redsocks_start_relay(client); } } static void httpr_relay_write_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = red_payload(client); int len = 0; assert(client->state >= httpr_recv_request_headers); redsocks_touch_client(client); if (client->state == httpr_recv_request_headers) { if (httpr->firstline) { len = bufferevent_write(client->relay, httpr->firstline, strlen(httpr->firstline)); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } http_auth *auth = red_http_auth(client->instance); ++auth->last_auth_count; const char *auth_scheme = NULL; char *auth_string = NULL; if (auth->last_auth_query != NULL) { /* find previous auth challange */ if (strncasecmp(auth->last_auth_query, "Basic", 5) == 0) { auth_string = basic_authentication_encode(client->instance->config.login, client->instance->config.password); auth_scheme = "Basic"; } else if (strncasecmp(auth->last_auth_query, "Digest", 6) == 0 && httpr->firstline) { /* calculate method & uri */ char *ptr = strchr(httpr->firstline, ' '), *ptr2; char *method = calloc(ptr - httpr->firstline + 1, 1); memcpy(method, httpr->firstline, ptr - httpr->firstline); method[ptr - httpr->firstline] = 0; ptr = strchr(httpr->firstline, '/'); if (!ptr || *++ptr != '/') { free(method); redsocks_log_error(client, LOG_NOTICE, "malformed request came"); redsocks_drop_client(client); return; } if (!(ptr = strchr(++ptr, '/')) || !(ptr2 = strchr(ptr, ' '))) { free(method); redsocks_log_error(client, LOG_NOTICE, "malformed request came"); redsocks_drop_client(client); return; } char *uri = calloc(ptr2 - ptr + 1, 1); memcpy(uri, ptr, ptr2 - ptr); uri[ptr2 - ptr] = 0; /* prepare an random string for cnounce */ char cnounce[17]; snprintf(cnounce, sizeof(cnounce), "%08x%08x", red_randui32(), red_randui32()); auth_string = digest_authentication_encode(auth->last_auth_query + 7, //line client->instance->config.login, client->instance->config.password, //user, pass method, uri, auth->last_auth_count, cnounce); // method, path, nc, cnounce free(method); free(uri); auth_scheme = "Digest"; } } if (auth_string != NULL) { len = 0; len |= bufferevent_write(client->relay, auth_response_header, strlen(auth_response_header)); len |= bufferevent_write(client->relay, " ", 1); len |= bufferevent_write(client->relay, auth_scheme, strlen(auth_scheme)); len |= bufferevent_write(client->relay, " ", 1); len |= bufferevent_write(client->relay, auth_string, strlen(auth_string)); len |= bufferevent_write(client->relay, "\r\n", 2); if (len) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } free(auth_string); len = bufferevent_write(client->relay, httpr->client_buffer.buff, httpr->client_buffer.len); if (len < 0) { redsocks_log_errno(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } client->state = httpr_request_sent; bufferevent_setwatermark(buffev, EV_READ, 1, HTTP_HEAD_WM_HIGH); bufferevent_enable(buffev, EV_READ); } } // drops client on failure static int httpr_append_header(redsocks_client *client, char *line) { httpr_client *httpr = red_payload(client); if (httpr_buffer_append(&httpr->client_buffer, line, strlen(line)) != 0) return -1; if (httpr_buffer_append(&httpr->client_buffer, "\x0d\x0a", 2) != 0) return -1; return 0; } // This function is not reenterable static char *fmt_http_host(struct sockaddr_in addr) { static char host[] = "123.123.123.123:12345"; if (ntohs(addr.sin_port) == 80) return inet_ntoa(addr.sin_addr); else { snprintf(host, sizeof(host), "%s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) ); return host; } } static int httpr_toss_http_firstline(redsocks_client *client) { httpr_client *httpr = red_payload(client); char *uri = NULL; char *host = httpr->has_host ? httpr->host : fmt_http_host(client->destaddr); assert(httpr->firstline); uri = strchr(httpr->firstline, ' '); if (uri) uri += 1; // one char further else { redsocks_log_error(client, LOG_NOTICE, "malformed request came"); goto fail; } httpr_buffer nbuff; if (httpr_buffer_init(&nbuff) != 0) { redsocks_log_error(client, LOG_ERR, "httpr_buffer_init"); goto fail; } if (httpr_buffer_append(&nbuff, httpr->firstline, uri - httpr->firstline) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, "http://", 7) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, host, strlen(host)) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, uri, strlen(uri)) != 0) goto addition_fail; if (httpr_buffer_append(&nbuff, "\x0d\x0a", 2) != 0) goto addition_fail; free(httpr->firstline); httpr->firstline = calloc(nbuff.len + 1, 1); strcpy(httpr->firstline, nbuff.buff); httpr_buffer_fini(&nbuff); return 0; addition_fail: httpr_buffer_fini(&nbuff); fail: redsocks_log_error(client, LOG_ERR, "httpr_toss_http_firstline"); return -1; } static void httpr_client_read_content(struct bufferevent *buffev, redsocks_client *client) { httpr_client *httpr = red_payload(client); static int post_buffer_len = 64 * 1024; char *post_buffer = calloc(post_buffer_len, 1); if (!post_buffer) { redsocks_log_error(client, LOG_ERR, "run out of memory"); redsocks_drop_client(client); return; } int error; while (true) { error = evbuffer_remove(buffev->input, post_buffer, post_buffer_len); if (error < 0) { free(post_buffer); redsocks_log_error(client, LOG_ERR, "evbuffer_remove"); redsocks_drop_client(client); return; } if (error == 0) break; httpr_buffer_append(&httpr->client_buffer, post_buffer, error); if (client->relay && client->state >= httpr_request_sent) { if (bufferevent_write(client->relay, post_buffer, error) != 0) { free(post_buffer); redsocks_log_error(client, LOG_ERR, "bufferevent_write"); redsocks_drop_client(client); return; } } } free(post_buffer); } static void httpr_client_read_cb(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; httpr_client *httpr = red_payload(client); redsocks_touch_client(client); if (client->state >= httpr_recv_request_headers) { httpr_client_read_content(buffev, client); return; } char *line = NULL; int connect_relay = 0; while (!connect_relay && (line = redsocks_evbuffer_readline(buffev->input))) { int skip_line = 0; int do_drop = 0; if (strlen(line) > 0) { if (!httpr->firstline) { httpr->firstline = line; line = 0; } else if (strncasecmp(line, "Host", 4) == 0) { httpr->has_host = 1; char *ptr = line + 5; while (*ptr && isspace(*ptr)) ptr ++; httpr->host = calloc(strlen(ptr) + 1, 1); strcpy(httpr->host, ptr); } else if (strncasecmp(line, "Proxy-Connection", 16) == 0) skip_line = 1; else if (strncasecmp(line, "Connection", 10) == 0) skip_line = 1; } else { // last line of request if (!httpr->firstline || httpr_toss_http_firstline(client) < 0) do_drop = 1; if (!httpr->has_host) { char host[32]; // "Host: 123.456.789.012:34567" int written_wo_null = snprintf(host, sizeof(host), "Host: %s", fmt_http_host(client->destaddr)); UNUSED(written_wo_null); assert(0 < written_wo_null && written_wo_null < sizeof(host)); if (httpr_append_header(client, host) < 0) do_drop = 1; } if (httpr_append_header(client, "Proxy-Connection: close") < 0) do_drop = 1; if (httpr_append_header(client, "Connection: close") < 0) do_drop = 1; connect_relay = 1; } if (line && !skip_line) if (httpr_append_header(client, line) < 0) do_drop = 1; free(line); if (do_drop) { redsocks_drop_client(client); return; } } if (connect_relay) { client->state = httpr_recv_request_headers; httpr_client_read_content(buffev, client); redsocks_connect_relay(client); } } static void httpr_connect_relay(redsocks_client *client) { int error; client->client->readcb = httpr_client_read_cb; error = bufferevent_enable(client->client, EV_READ); if (error) { redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); redsocks_drop_client(client); } } relay_subsys http_relay_subsys = { .name = "http-relay", .payload_len = sizeof(httpr_client), .instance_payload_len = sizeof(http_auth), .init = httpr_client_init, .fini = httpr_client_fini, .connect_relay = httpr_connect_relay, .readcb = httpr_relay_read_cb, .writecb = httpr_relay_write_cb, .instance_init = httpr_instance_init, .instance_fini = httpr_instance_fini, }; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ redsocks-release-0.5/Makefile0000644000175000017500000000561613030520742016151 0ustar apoikosapoikos-include make.conf OBJS := parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o base64.o md5.o http-auth.o utils.o redudp.o dnstc.o gen/version.o ifeq ($(DBG_BUILD),1) OBJS += debug.o endif SRCS := $(OBJS:.o=.c) CONF := config.h DEPS := .depend OUT := redsocks VERSION := 0.5 LIBS := -levent_core ifeq ($(DBG_BUILD),1) # -levent_extra is required only for `http` and `debug` LIBS += -levent_extra endif CFLAGS += -g -O2 # _GNU_SOURCE is used to get splice(2), it also implies _BSD_SOURCE override CFLAGS += -std=c99 -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE -D_GNU_SOURCE -Wall all: $(OUT) .PHONY: all clean distclean test tags: *.c *.h ctags -R $(CONF): @case `uname` in \ Linux*) \ echo "#define USE_IPTABLES" >$(CONF) \ ;; \ OpenBSD) \ echo "#define USE_PF" >$(CONF) \ ;; \ *) \ echo "Unknown system, only generic firewall code is compiled" 1>&2; \ echo "/* Unknown system, only generic firewall code is compiled */" >$(CONF) \ ;; \ esac ifeq ($(DBG_BUILD),1) echo "#define DBG_BUILD 1" >>$(CONF) endif # Dependency on .git is useful to rebuild `version.c' after commit, but it breaks non-git builds. gen/version.c: *.c *.h gen/.build rm -f $@.tmp echo '/* this file is auto-generated during build */' > $@.tmp echo '#include "../version.h"' >> $@.tmp echo 'const char* redsocks_version = ' >> $@.tmp if [ -d .git ]; then \ echo '"redsocks.git/'`git describe --tags`'"'; \ if [ `git status --porcelain | grep -v -c '^??'` != 0 ]; then \ echo '"'"-unclean-$$(date --rfc-3339=seconds | tr ' ' 'T')-$${USER}@$$(uname -n)"'"'; \ fi \ else \ echo '"redsocks/$(VERSION)"'; \ fi >> $@.tmp echo ';' >> $@.tmp mv -f $@.tmp $@ gen/.build: mkdir -p gen touch $@ base.c: $(CONF) $(DEPS): $(SRCS) gcc -MM $(SRCS) 2>/dev/null >$(DEPS) || \ ( \ for I in $(wildcard *.h); do \ export $${I//[-.]/_}_DEPS="`sed '/^\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$I`"; \ done; \ echo -n >$(DEPS); \ for SRC in $(SRCS); do \ echo -n "$${SRC%.c}.o: " >>$(DEPS); \ export SRC_DEPS="`sed '/\#[ \t]*include \?"\(.*\)".*/!d;s//\1/' $$SRC | sort`"; \ while true; do \ export SRC_DEPS_OLD="$$SRC_DEPS"; \ export SRC_DEEP_DEPS=""; \ for HDR in $$SRC_DEPS; do \ eval export SRC_DEEP_DEPS="\"$$SRC_DEEP_DEPS \$$$${HDR//[-.]/_}_DEPS\""; \ done; \ export SRC_DEPS="`echo $$SRC_DEPS $$SRC_DEEP_DEPS | sed 's/ */\n/g' | sort -u`"; \ test "$$SRC_DEPS" = "$$SRC_DEPS_OLD" && break; \ done; \ echo $$SRC $$SRC_DEPS >>$(DEPS); \ done; \ ) -include $(DEPS) $(OUT): $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) clean: $(RM) $(OUT) $(CONF) $(OBJS) distclean: clean $(RM) tags $(DEPS) $(RM) -r gen tests/__build-tstamp__: $(OUT) tests/[a-z]* tests/[a-z]*/* cd tests && ./build touch $@ tests/prlimit-nofile: tests/prlimit-nofile.c $(CC) $(CFLAGS) -o $@ $^ test: tests/__build-tstamp__ tests/prlimit-nofile cd tests && env $(TEST_ENV) ./run redsocks-release-0.5/redsocks.c0000644000175000017500000013014713030520742016470 0ustar apoikosapoikos/* redsocks - transparent TCP-to-proxy redirector * Copyright (C) 2007-2011 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 on = 1; int fd = -1; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; } error = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (error) { log_errno(LOG_ERR, "setsockopt"); goto fail; } error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); if (error) { log_errno(LOG_ERR, "bind"); goto fail; } error = fcntl_nonblock(fd); if (error) { log_errno(LOG_ERR, "fcntl"); 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={,}: */ redsocks-release-0.5/http-auth.h0000644000175000017500000000163213030520742016572 0ustar apoikosapoikos#ifndef HTTP_AUTH_H #define HTTP_AUTH_H #include "redsocks.h" typedef struct http_auth_t { char *last_auth_query; int last_auth_count; } http_auth; static inline http_auth* red_http_auth(redsocks_instance *i) { return (http_auth*)(i + 1); } /* * Create the authentication header contents for the `Basic' scheme. * This is done by encoding the string "USER:PASS" to base64 and * prepending the string "Basic " in front of it. * */ char* basic_authentication_encode(const char *user, const char *passwd); /* * Create the authentication header contents for the 'Digest' scheme. * only md5 method is available, see RFC 2617 for detail. * */ char* digest_authentication_encode(const char *line, const char *user, const char *passwd, const char *method, const char *path, int count, const char *cnonce); char *http_auth_request_header(struct evbuffer *src, struct evbuffer *tee); #endif /* HTTP_AUTH_H */ redsocks-release-0.5/debian/0000755000175000017500000000000013030520742015723 5ustar apoikosapoikosredsocks-release-0.5/redudp.h0000644000175000017500000000250713030520742016141 0ustar apoikosapoikos#ifndef REDUDP_H #define REDUDP_H typedef struct redudp_config_t { struct sockaddr_in bindaddr; struct sockaddr_in relayaddr; // TODO: outgoingaddr; struct sockaddr_in destaddr; char *login; char *password; uint16_t max_pktqueue; uint16_t udp_timeout; uint16_t udp_timeout_stream; } redudp_config; typedef struct redudp_instance_t { list_head list; redudp_config config; struct event listener; list_head clients; } redudp_instance; typedef struct redudp_client_t { list_head list; redudp_instance *instance; struct sockaddr_in clientaddr; struct sockaddr_in destaddr; int sender_fd; // shared between several clients socket (bound to `destaddr`) struct event timeout; struct bufferevent *relay; struct event udprelay; struct sockaddr_in udprelayaddr; int state; // it's used by bottom layer time_t first_event; time_t last_client_event; time_t last_relay_event; unsigned int queue_len; list_head queue; } redudp_client; typedef struct enqueued_packet_t { list_head list; size_t len; char data[1]; } enqueued_packet; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* REDUDP_H */ redsocks-release-0.5/log.h0000644000175000017500000000165213030520742015437 0ustar apoikosapoikos#ifndef LOG_H_WED_JAN_24_18_21_27_2007 #define LOG_H_WED_JAN_24_18_21_27_2007 #include #include #include #define log_errno(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 1, prio, ## msg) #define log_error(prio, msg...) _log_write(__FILE__, __LINE__, __func__, 0, prio, ## msg) extern const char *error_lowmem; int log_preopen(const char *dst, bool log_debug, bool log_info); void log_open(); bool should_log(int priority); void _log_vwrite(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, va_list ap); void _log_write(const char *file, int line, const char *func, int do_errno, int priority, const char *fmt, ...) #if defined(__GNUC__) __attribute__ (( format (printf, 6, 7) )) #endif ; /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ #endif /* LOG_H_WED_JAN_24_18_21_27_2007 */ redsocks-release-0.5/list.h0000644000175000017500000003051313030520742015627 0ustar apoikosapoikos#ifndef _LINUX_LIST_H #define _LINUX_LIST_H /** That's taken from linux-2.6.18.2 source tree with light modifications * I don't know what copyright to place here but that's GPLv2 code so * I assume I'm free to reuse it. */ #include "utils.h" // container_of /* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */ #define LIST_POISON1 ((void *) 0x00100100) #define LIST_POISON2 ((void *) 0x00200200) /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ typedef struct list_head_t { struct list_head_t *next, *prev; } list_head; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head_t name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head_t *list) { list->next = list; list->prev = list; } /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_add(struct list_head_t *new, struct list_head_t *prev, struct list_head_t *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } /** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head_t *new, struct list_head_t *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head_t *new, struct list_head_t *head) { __list_add(new, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head_t * prev, struct list_head_t * next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty on entry does not return true after this, the entry is * in an undefined state. */ static inline void list_del(struct list_head_t *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } /** * list_replace - replace old entry by new one * @old : the element to be replaced * @new : the new element to insert * Note: if 'old' was empty, it will be overwritten. */ static inline void list_replace(struct list_head_t *old, struct list_head_t *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } static inline void list_replace_init(struct list_head_t *old, struct list_head_t *new) { list_replace(old, new); INIT_LIST_HEAD(old); } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head_t *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head_t *list, struct list_head_t *head) { __list_del(list->prev, list->next); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head_t *list, struct list_head_t *head) { __list_del(list->prev, list->next); list_add_tail(list, head); } /** * list_is_last - tests whether @list is the last entry in list @head * @list: the entry to test * @head: the head of the list */ static inline int list_is_last(const struct list_head_t *list, const struct list_head_t *head) { return list->next == head; } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head_t *head) { return head->next == head; } /** * list_empty_careful - tests whether a list is empty and not being modified * @head: the list to test * * Description: * tests whether a list is empty _and_ checks that no other CPU might be * in the process of modifying either member (next or prev) * * NOTE: using list_empty_careful() without synchronization * can only be safe if the only activity that can happen * to the list entry is list_del_init(). Eg. it cannot be used * if another CPU could re-list_add() it. */ static inline int list_empty_careful(const struct list_head_t *head) { struct list_head_t *next = head->next; return (next == head) && (next == head->prev); } static inline void __list_splice(struct list_head_t *list, struct list_head_t *head) { struct list_head_t *first = list->next; struct list_head_t *last = list->prev; struct list_head_t *at = head->next; first->prev = head; head->next = first; last->next = at; at->prev = last; } /** * list_splice - join two lists * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(struct list_head_t *list, struct list_head_t *head) { if (!list_empty(list)) __list_splice(list, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head_t *list, struct list_head_t *head) { if (!list_empty(list)) { __list_splice(list, head); INIT_LIST_HEAD(list); } } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head_t pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_struct within the struct. */ #define list_entry(ptr, type, member) \ container_of(ptr, type, member) /** * list_for_each - iterate over a list * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) /** * __list_for_each - iterate over a list * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. * * This variant differs from list_for_each() in that it's the * simplest possible list iteration code, no prefetching is done. * Use this for code that knows the list to be very short (empty * or 1 entry) most of the time. */ #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head_t to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); \ pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head_t to use as a loop cursor. * @n: another &struct list_head_t to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, __typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, __typeof(*pos), member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_entry((head)->prev, __typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.prev, __typeof(*pos), member)) /** * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_struct within the struct. * * Prepares a pos entry for use as a start point in list_for_each_entry_continue. */ #define list_prepare_entry(pos, head, member) \ ((pos) ? : list_entry(head, __typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, __typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, __typeof(*pos), member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); \ pos = list_entry(pos->member.next, __typeof(*pos), member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_entry((head)->next, __typeof(*pos), member), \ n = list_entry(pos->member.next, __typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, __typeof(*n), member)) /** * list_for_each_entry_safe_continue * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_entry(pos->member.next, __typeof(*pos), member), \ n = list_entry(pos->member.next, __typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, __typeof(*n), member)) /** * list_for_each_entry_safe_from * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_entry(pos->member.next, __typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.next, __typeof(*n), member)) /** * list_for_each_entry_safe_reverse * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_struct within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_entry((head)->prev, __typeof(*pos), member), \ n = list_entry(pos->member.prev, __typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, __typeof(*n), member)) #endif