From c50dd8b2b4bb89afa12aaffffc91af6c12d14051 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Sat, 27 Nov 2010 21:57:43 +0200 Subject: [PATCH 01/10] Some source code cleanup. --- Makefile | 2 +- redsocks.c | 94 ++++------------------------------------------- utils.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++ utils.h | 7 ++++ 4 files changed, 122 insertions(+), 87 deletions(-) create mode 100644 utils.c diff --git a/Makefile b/Makefile index 3411476..0bdb83e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS=-std=gnu99 -Wall -g -O0 .PHONY: all all: redsocks -obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o +obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o utils.o src = $(patsubst %.o,%.c,$(obj)) redsocks: $(obj) diff --git a/redsocks.c b/redsocks.c index abb551c..0969094 100644 --- a/redsocks.c +++ b/redsocks.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -41,26 +40,6 @@ static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how); -/** 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) - */ -static 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; -} - extern relay_subsys http_connect_subsys; extern relay_subsys http_relay_subsys; extern relay_subsys socks4_subsys; @@ -73,7 +52,7 @@ static relay_subsys *relay_subsystems[] = &socks5_subsys, }; -list_head instances = LIST_HEAD_INIT(instances); +static list_head instances = LIST_HEAD_INIT(instances); static parser_entry redsocks_entries[] = { @@ -197,16 +176,6 @@ void redsocks_log_write( va_end(ap); } -static 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; -} - - void redsocks_touch_client(redsocks_client *client) { redsocks_time(&client->last_event); @@ -375,15 +344,9 @@ static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffe // I assume that -1 is invalid errno value static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev) { - int error; - int pseudo_errno; - size_t optlen = sizeof(pseudo_errno); - - assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); - - error = getsockopt(EVENT_FD(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, &optlen); - if (error) { - redsocks_log_errno(client, LOG_ERR, "getsockopt"); + 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; @@ -543,53 +506,12 @@ fail: void redsocks_connect_relay(redsocks_client *client) { - int on = 1; - int relay_fd = -1; - int error; - - relay_fd = socket(AF_INET, SOCK_STREAM, 0); - if (relay_fd == -1) { - redsocks_log_errno(client, LOG_ERR, "socket"); - goto fail; - } - - error = fcntl_nonblock(relay_fd); - if (error) { - redsocks_log_errno(client, LOG_ERR, "fcntl"); - goto fail; - } - - error = setsockopt(relay_fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); - if (error) { - redsocks_log_errno(client, LOG_WARNING, "setsockopt"); - goto fail; - } - - error = connect(relay_fd, (struct sockaddr*)&client->instance->config.relayaddr, sizeof(client->instance->config.relayaddr)); - if (error && errno != EINPROGRESS) { - redsocks_log_errno(client, LOG_NOTICE, "connect"); - goto fail; - } - - client->relay = bufferevent_new(relay_fd, NULL, redsocks_relay_connected, redsocks_event_error, 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, "bufferevent_new"); - goto fail; + redsocks_log_errno(client, LOG_ERR, "red_connect_relay"); + redsocks_drop_client(client); } - relay_fd = -1; - - error = bufferevent_enable(client->relay, EV_WRITE); // we wait for connection... - if (error) { - redsocks_log_errno(client, LOG_ERR, "bufferevent_enable"); - goto fail; - } - - return; // OK - -fail: - if (relay_fd != -1) - close(relay_fd); - redsocks_drop_client(client); } static void redsocks_accept_client(int fd, short what, void *_arg) diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..39df3a8 --- /dev/null +++ b/utils.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include "log.h" +#include "utils.h" + +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; +} + +struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg) +{ + struct bufferevent *retval = NULL; + int on = 1; + 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; + } + + error = setsockopt(relay_fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); + if (error) { + log_errno(LOG_WARNING, "setsockopt"); + 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; + } + + 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) + close(relay_fd); + if (retval) + bufferevent_free(retval); + return NULL; +} + +int red_socket_geterrno(struct bufferevent *buffev) +{ + int error; + int pseudo_errno; + size_t optlen = sizeof(pseudo_errno); + + assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write)); + + error = getsockopt(EVENT_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; +} + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/utils.h b/utils.h index b91b8ba..057d6c0 100644 --- a/utils.h +++ b/utils.h @@ -2,6 +2,9 @@ #define UTILS_H_SAT_FEB__2_02_24_05_2008 #include +#include +#include +#include #define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0])) @@ -20,6 +23,10 @@ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) +time_t redsocks_time(time_t *t); +struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); +int red_socket_geterrno(struct bufferevent *buffev); +int fcntl_nonblock(int fd); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ /* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ From 8ad8d7eb41d25ca44ff85c5eb3ec0ae1f29992e7 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Sat, 27 Nov 2010 23:20:46 +0200 Subject: [PATCH 02/10] More source code cleanup. --- redsocks.c | 12 ++---------- utils.c | 17 +++++++++++++++++ utils.h | 2 ++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/redsocks.c b/redsocks.c index 0969094..19b73c6 100644 --- a/redsocks.c +++ b/redsocks.c @@ -477,21 +477,13 @@ void redsocks_write_helper( static void redsocks_relay_connected(struct bufferevent *buffev, void *_arg) { redsocks_client *client = _arg; - int pseudo_errno; assert(buffev == client->relay); redsocks_touch_client(client); - pseudo_errno = redsocks_socket_geterrno(client, buffev); - if (pseudo_errno == -1) { - redsocks_log_errno(client, LOG_NOTICE, "redsocks_socket_geterrno"); - goto fail; - } - - if (pseudo_errno) { - errno = pseudo_errno; - redsocks_log_errno(client, LOG_NOTICE, "connect"); + if (!red_is_socket_connected_ok(buffev)) { + redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); goto fail; } diff --git a/utils.c b/utils.c index 39df3a8..fef54d7 100644 --- a/utils.c +++ b/utils.c @@ -103,4 +103,21 @@ int fcntl_nonblock(int fd) 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; + } +} + /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ diff --git a/utils.h b/utils.h index 057d6c0..d7ae73b 100644 --- a/utils.h +++ b/utils.h @@ -26,6 +26,8 @@ time_t redsocks_time(time_t *t); 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 fcntl_nonblock(int fd); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ From 6e574a2021ec1662229c2362761271f436be260c Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Sat, 27 Nov 2010 23:30:58 +0200 Subject: [PATCH 03/10] Another source code cleanup. --- redsocks.c | 9 ++------- utils.h | 9 +++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/redsocks.c b/redsocks.c index 19b73c6..befc4e6 100644 --- a/redsocks.c +++ b/redsocks.c @@ -373,14 +373,9 @@ static void redsocks_event_error(struct bufferevent *buffev, short what, void *_ } else { errno = redsocks_socket_geterrno(client, buffev); - redsocks_log_errno(client, LOG_NOTICE, "%s error, code %s|%s|%s|%s|%s == %X", + redsocks_log_errno(client, LOG_NOTICE, "%s error, code " event_fmt_str, buffev == client->relay ? "relay" : "client", - 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); + event_fmt(what)); redsocks_drop_client(client); } } diff --git a/utils.h b/utils.h index d7ae73b..e87fd6e 100644 --- a/utils.h +++ b/utils.h @@ -30,6 +30,15 @@ int red_is_socket_connected_ok(struct bufferevent *buffev); 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) + /* 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 */ From 9c8f21a986d93afe1211f32b14eb5270cc91cc3c Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Sun, 28 Nov 2010 23:15:56 +0200 Subject: [PATCH 04/10] Further cleanup. --- socks5.c | 45 +++++++++++++++++++++++++++++++-------------- socks5.h | 10 ++++++++++ 2 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 socks5.h diff --git a/socks5.c b/socks5.c index 836ea49..edca1b9 100644 --- a/socks5.c +++ b/socks5.c @@ -21,6 +21,7 @@ #include "utils.h" #include "log.h" #include "redsocks.h" +#include "socks5.h" typedef enum socks5_state_t { socks5_new, @@ -125,36 +126,49 @@ const char *socks5_strstatus[] = { "address type not supported", }; +int socks5_is_valid_cred(const char *login, const char *password) +{ + if (!login || !password) + return 0; + if (strlen(login) > 255) { + log_error(LOG_WARNING, "Socks5 login can't be more than 255 chars, <%s> is too long", login); + return 0; + } + if (strlen(password) > 255) { + log_error(LOG_WARNING, "Socks5 password can't be more than 255 chars, <%s> is too long", password); + return 0; + } + return 1; +} + void socks5_client_init(redsocks_client *client) { socks5_client *socks5 = (void*)(client + 1); const redsocks_config *config = &client->instance->config; client->state = socks5_new; - socks5->do_password = 0; - if (config->login && config->password) { - if (strlen(config->login) > 255) - redsocks_log_error(client, LOG_WARNING, "Socks5 login can't be more than 255 chars"); - else if (strlen(config->password) > 255) - redsocks_log_error(client, LOG_WARNING, "Socks5 password can't be more than 255 chars"); - else - socks5->do_password = 1; - } + socks5->do_password = socks5_is_valid_cred(config->login, config->password); } static struct evbuffer *socks5_mkmethods(redsocks_client *client) { socks5_client *socks5 = (void*)(client + 1); - int len = sizeof(socks5_method_req) + socks5->do_password; + 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; union { socks5_method_req req; uint8_t raw[len]; } u; u.req.ver = socks5_ver; - u.req.num_methods = 1 + socks5->do_password; + u.req.num_methods = 1 + do_password; u.req.methods[0] = socks5_auth_none; - if (socks5->do_password) + if (do_password) u.req.methods[1] = socks5_auth_password; return mkevbuffer(&u.req, len); @@ -162,8 +176,11 @@ static struct evbuffer *socks5_mkmethods(redsocks_client *client) static struct evbuffer *socks5_mkpassword(redsocks_client *client) { - const char *login = client->instance->config.login; - const char *password = client->instance->config.password; + 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; diff --git a/socks5.h b/socks5.h new file mode 100644 index 0000000..85dcee3 --- /dev/null +++ b/socks5.h @@ -0,0 +1,10 @@ +#ifndef SOCKS5_H +#define SOCKS5_H + +struct evbuffer *socks5_mkmethods_plain(int do_password); +struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password); + + +/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ +/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ +#endif /* SOCKS5_H */ From 45cab3de31878efc012b5a7e53be2885f871f12f Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Mon, 29 Nov 2010 00:09:48 +0200 Subject: [PATCH 05/10] This cleanup commit introduces ex-plain function! --- redsocks.c | 29 ++++++++++++++++++++++------- redsocks.h | 9 +++++++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/redsocks.c b/redsocks.c index befc4e6..755c8a7 100644 --- a/redsocks.c +++ b/redsocks.c @@ -429,27 +429,40 @@ fail: return retval; } -void redsocks_write_helper_ex( +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(client); + buff = mkmessage(p); if (!buff) goto fail; len = bufferevent_write_buffer(client->relay, buff); if (len < 0) { - redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + if (client) + redsocks_log_errno(client, LOG_ERR, "bufferevent_write_buffer"); + else + log_errno(LOG_ERR, "bufferevent_write_buffer"); goto fail; } } - client->state = state; + if (client) + client->state = state; buffev->wm_read.low = wm_low; buffev->wm_read.high = wm_high; bufferevent_enable(buffev, EV_READ); @@ -458,15 +471,17 @@ void redsocks_write_helper_ex( fail: if (buff) evbuffer_free(buff); - if (drop) + if (drop && client) redsocks_drop_client(client); + return drop ? -1 : 0; } -void redsocks_write_helper( +int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only) { - redsocks_write_helper_ex(buffev, client, mkmessage, state, wm_only, 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) diff --git a/redsocks.h b/redsocks.h index f4f9f9d..53974a5 100644 --- a/redsocks.h +++ b/redsocks.h @@ -70,11 +70,16 @@ int sizes_greater_equal(size_t a, size_t b); 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); -void redsocks_write_helper_ex( +/* 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); -void redsocks_write_helper( +int redsocks_write_helper( struct bufferevent *buffev, redsocks_client *client, redsocks_message_maker mkmessage, int state, size_t wm_only); From 197d37c382dddcfbca57ba902cb9f9fbada6b0b8 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Mon, 29 Nov 2010 00:50:19 +0200 Subject: [PATCH 06/10] Another minor cleanup. --- socks5.c | 25 +++++++++++++++++-------- socks5.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/socks5.c b/socks5.c index edca1b9..3d65f26 100644 --- a/socks5.c +++ b/socks5.c @@ -224,15 +224,29 @@ static void socks5_write_cb(struct bufferevent *buffev, void *_arg) } } +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; - if (reply.ver != socks5_ver) { - redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected auth methods reply version..."); + error = socks5_is_known_auth_method(&reply, socks5->do_password); + if (error) { + redsocks_log_error(client, LOG_NOTICE, error); redsocks_drop_client(client); } else if (reply.method == socks5_auth_none) { @@ -241,17 +255,12 @@ static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply) ); } - else if (reply.method == socks5_auth_password && socks5->do_password) { + else if (reply.method == socks5_auth_password) { redsocks_write_helper( buffev, client, socks5_mkpassword, socks5_auth_sent, sizeof(socks5_auth_reply) ); } - else { - if (reply.method != socks5_auth_invalid) - redsocks_log_error(client, LOG_NOTICE, "Socks5 server requested unexpected auth method..."); - redsocks_drop_client(client); - } } static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5) diff --git a/socks5.h b/socks5.h index 85dcee3..f455fef 100644 --- a/socks5.h +++ b/socks5.h @@ -3,6 +3,7 @@ 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); /* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ From 94c96a60ef1661af12e6fc5a3de0ff00cb1f8bcf Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Thu, 2 Dec 2010 23:16:23 +0200 Subject: [PATCH 07/10] Cleanup client session logging. --- redsocks.c | 13 +++++++------ redsocks.h | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/redsocks.c b/redsocks.c index 755c8a7..6b6a1aa 100644 --- a/redsocks.c +++ b/redsocks.c @@ -141,9 +141,10 @@ static parser_section redsocks_conf_section = .onexit = redsocks_onexit }; -void redsocks_log_write( +void redsocks_log_write_plain( const char *file, int line, const char *func, int do_errno, - redsocks_client *client, int priority, const char *orig_fmt, ... + const struct sockaddr_in *clientaddr, const struct sockaddr_in *destaddr, + int priority, const char *orig_fmt, ... ) { int saved_errno = errno; struct evbuffer *fmt = evbuffer_new(); @@ -155,15 +156,15 @@ void redsocks_log_write( // no return, as I have to call va_start/va_end } - if (!inet_ntop(client->clientaddr.sin_family, &client->clientaddr.sin_addr, clientaddr_str, sizeof(clientaddr_str))) + if (!inet_ntop(clientaddr->sin_family, &clientaddr->sin_addr, clientaddr_str, sizeof(clientaddr_str))) strncpy(clientaddr_str, "???", sizeof(clientaddr_str)); - if (!inet_ntop(client->destaddr.sin_family, &client->destaddr.sin_addr, destaddr_str, sizeof(destaddr_str))) + if (!inet_ntop(destaddr->sin_family, &destaddr->sin_addr, destaddr_str, sizeof(destaddr_str))) strncpy(destaddr_str, "???", sizeof(destaddr_str)); if (fmt) { evbuffer_add_printf(fmt, "[%s:%i->%s:%i]: %s", - clientaddr_str, ntohs(client->clientaddr.sin_port), - destaddr_str, ntohs(client->destaddr.sin_port), + clientaddr_str, ntohs(clientaddr->sin_port), + destaddr_str, ntohs(destaddr->sin_port), orig_fmt); } diff --git a/redsocks.h b/redsocks.h index 53974a5..b042a31 100644 --- a/redsocks.h +++ b/redsocks.h @@ -84,9 +84,18 @@ int redsocks_write_helper( redsocks_message_maker mkmessage, int state, size_t wm_only); -#define redsocks_log_error(client, prio, msg...) redsocks_log_write(__FILE__, __LINE__, __func__, 0, client, prio, ## msg) -#define redsocks_log_errno(client, prio, msg...) redsocks_log_write(__FILE__, __LINE__, __func__, 1, client, prio, ## msg) -void redsocks_log_write(const char *file, int line, const char *func, int do_errno, redsocks_client *client, int priority, const char *fmt, ...); +#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={,}: */ From bce12d59dd35698997f889b647c792237fce5680 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Thu, 2 Dec 2010 23:30:21 +0200 Subject: [PATCH 08/10] Fix bug introduced in 45cab3. --- redsocks.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/redsocks.c b/redsocks.c index 6b6a1aa..cd62864 100644 --- a/redsocks.c +++ b/redsocks.c @@ -452,7 +452,8 @@ int redsocks_write_helper_ex_plain( if (!buff) goto fail; - len = bufferevent_write_buffer(client->relay, buff); + 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"); From 2e35ae49c73ea584b0f9a5bfcf2114de1dddbfa5 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Thu, 2 Dec 2010 23:39:03 +0200 Subject: [PATCH 09/10] Cleanup: make socks5 structures reusable. --- redsocks.h | 6 ---- socks5.c | 104 ++++++++++++----------------------------------------- socks5.h | 93 +++++++++++++++++++++++++++++++++++++++++++++++ utils.h | 6 ++++ 4 files changed, 122 insertions(+), 87 deletions(-) diff --git a/redsocks.h b/redsocks.h index b042a31..3b19655 100644 --- a/redsocks.h +++ b/redsocks.h @@ -6,12 +6,6 @@ #include #include "list.h" -#if defined __GNUC__ -#define PACKED __attribute__((packed)) -#else -#error Unknown compiler, modify types.h for it -#endif - struct redsocks_client_t; diff --git a/socks5.c b/socks5.c index 3d65f26..70d27d8 100644 --- a/socks5.c +++ b/socks5.c @@ -38,82 +38,6 @@ typedef struct socks5_client_t { int to_skip; // valid while reading last reply (after main request) } socks5_client; -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; - -const int socks5_ver = 5; - -const int socks5_auth_none = 0x00; -const int socks5_auth_gssapi = 0x01; -const int socks5_auth_password = 0x02; -const int socks5_auth_invalid = 0xFF; - -typedef struct socks5_auth_reply_t { - uint8_t ver; - uint8_t status; -} PACKED socks5_auth_reply; - -const int socks5_password_ver = 0x01; -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; - -const int socks5_reply_maxlen = 512; // as domain name can't be longer than 256 bytes -const int socks5_cmd_connect = 1; -const int socks5_cmd_bind = 2; -const int socks5_cmd_udp_associate = 2; -const int socks5_addrtype_ipv4 = 1; -const int socks5_addrtype_domain = 3; -const int socks5_addrtype_ipv6 = 4; -const int socks5_status_succeeded = 0; -const int socks5_status_server_failure = 1; -const int socks5_status_connection_not_allowed_by_ruleset = 2; -const int socks5_status_Network_unreachable = 3; -const int socks5_status_Host_unreachable = 4; -const int socks5_status_Connection_refused = 5; -const int socks5_status_TTL_expired = 6; -const int socks5_status_Command_not_supported = 7; -const int socks5_status_Address_type_not_supported = 8; - const char *socks5_strstatus[] = { "ok", "server failure", @@ -125,6 +49,17 @@ const char *socks5_strstatus[] = { "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 ""; + } +} int socks5_is_valid_cred(const char *login, const char *password) { @@ -194,22 +129,29 @@ struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password return mkevbuffer(req, length); } -static struct evbuffer *socks5_mkconnect(redsocks_client *client) +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_connect; + req.head.cmd = socks5_cmd; req.head.reserved = 0; req.head.addrtype = socks5_addrtype_ipv4; - req.ip.addr = client->destaddr.sin_addr.s_addr; - req.ip.port = client->destaddr.sin_port; + 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; @@ -246,7 +188,7 @@ static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client error = socks5_is_known_auth_method(&reply, socks5->do_password); if (error) { - redsocks_log_error(client, LOG_NOTICE, 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) { diff --git a/socks5.h b/socks5.h index f455fef..5155225 100644 --- a/socks5.h +++ b/socks5.h @@ -1,10 +1,103 @@ #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); +int 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={,}: */ diff --git a/utils.h b/utils.h index e87fd6e..b796e8a 100644 --- a/utils.h +++ b/utils.h @@ -11,6 +11,12 @@ #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--) +#if defined __GNUC__ +#define PACKED __attribute__((packed)) +#else +#error Unknown compiler, modify utils.h for it +#endif + /** * container_of - cast a member of a structure out to the containing structure From c0d4cede5a43e552017f2696d5b80dc2741e3190 Mon Sep 17 00:00:00 2001 From: Leonid Evdokimov Date: Fri, 3 Dec 2010 02:18:14 +0200 Subject: [PATCH 10/10] Initial UDP over Socks5 support. --- Makefile | 2 +- README | 2 + main.c | 4 +- redsocks.conf.example | 27 +- redudp.c | 749 ++++++++++++++++++++++++++++++++++++++++++ redudp.h | 47 +++ 6 files changed, 827 insertions(+), 4 deletions(-) create mode 100644 redudp.c create mode 100644 redudp.h diff --git a/Makefile b/Makefile index 0bdb83e..6f7e3d0 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS=-std=gnu99 -Wall -g -O0 .PHONY: all all: redsocks -obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o utils.o +obj = parser.o main.o redsocks.o log.o http-connect.o socks4.o socks5.o http-relay.o base.o utils.o redudp.o src = $(patsubst %.o,%.c,$(obj)) redsocks: $(obj) diff --git a/README b/README index cd881bf..6cf1cd8 100644 --- a/README +++ b/README @@ -28,6 +28,8 @@ Features Redirect any TCP connection to SOCKS4, SOCKS5 or HTTPS (HTTP/CONNECT) proxy server. +Redirect UDP packets via SOCKS5 proxy server. + Login/password authentication is supported for SOCKS5 connections. SOCKS4 supports only username, password is ignored. diff --git a/main.c b/main.c index 959cf58..bf6b168 100644 --- a/main.c +++ b/main.c @@ -29,12 +29,12 @@ extern app_subsys redsocks_subsys; extern app_subsys base_subsys; -// extern app_subsys reddns_subsys; +extern app_subsys redudp_subsys; app_subsys *subsystems[] = { &redsocks_subsys, &base_subsys, -// &reddns_subsys, + &redudp_subsys, }; static const char *confname = "redsocks.conf"; diff --git a/redsocks.conf.example b/redsocks.conf.example index c2f86c9..ac20fe5 100644 --- a/redsocks.conf.example +++ b/redsocks.conf.example @@ -53,4 +53,29 @@ redsocks { // password = "baz"; } -// you can add one more `redsocks' section if you need. +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; + + // kernel does not give us this information, so we have to duplicate it + // in both iptables rules and configuration file. By the way, you can + // set `local_ip' to 127.45.67.89 if you need more than 65535 ports to + // forward ;-) + // This limitation may be relaxed in future versions using contrack-tools. + dest_ip = 8.8.8.8; + dest_port = 53; + + udp_timeout = 30; + udp_timeout_stream = 180; +} + +// you can add more `redsocks' and `redudp' sections if you need. diff --git a/redudp.c b/redudp.c new file mode 100644 index 0000000..b251d09 --- /dev/null +++ b/redudp.c @@ -0,0 +1,749 @@ +/* redsocks - transparent TCP-to-proxy redirector + * Copyright (C) 2007-2008 Leonid Evdokimov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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" + +#define redudp_log_error(client, prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg) +#define redudp_log_errno(client, prio, msg...) \ + redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->instance->config.destaddr, 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(); + +typedef struct redudp_expected_assoc_reply_t { + socks5_reply h; + socks5_addr_ipv4 ip; +} PACKED redudp_expected_assoc_reply; + +/*********************************************************************** + * Helpers + */ +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 = client->instance->config.destaddr.sin_addr.s_addr; + preamble->ip.port = client->instance->config.destaddr.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); +} + +static int recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr) +{ + socklen_t addrlen = sizeof(*inaddr); + ssize_t pktlen; + + pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen); + if (pktlen == -1) { + log_errno(LOG_WARNING, "recvfrom"); + return -1; + } + + if (addrlen != sizeof(*inaddr)) { + log_error(LOG_WARNING, "unexpected address length %u instead of %u", addrlen, sizeof(*inaddr)); + return -1; + } + + if (pktlen >= buflen) { + char buf[INET6_ADDRSTRLEN]; + const char *addr = inet_ntop(inaddr->sin_family, &inaddr->sin_addr, buf, sizeof(buf)); + log_error(LOG_WARNING, "wow! Truncated udp packet of size %u from %s:%u! impossible! dropping it...", + pktlen, addr ? addr : "?", ntohs(inaddr->sin_port)); + return -1; + } + + return pktlen; +} + +/*********************************************************************** + * Logic + */ +static void redudp_drop_client(redudp_client *client) +{ + int fd; + 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) { + fd = EVENT_FD(&client->relay->ev_read); + bufferevent_free(client->relay); + shutdown(fd, SHUT_RDWR); + close(fd); + } + if (event_initialized(&client->udprelay)) { + fd = EVENT_FD(&client->udprelay); + if (event_del(&client->udprelay) == -1) + redudp_log_errno(client, LOG_ERR, "event_del"); + close(fd); + } + 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_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 %u bytes, but only %u 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 %u", + 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) + 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 %u", + 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 %u", + 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; + redudp_log_error(client, LOG_DEBUG, ""); + + 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: %u, last_client: %u, last_relay: %u.", + 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, 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)); + timeout_set(&client->timeout, redudp_timeout, client); + // XXX: self->relay_ss->init(client); + + 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_FD(&client->udprelay)); + + pktlen = recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr); + if (pktlen == -1) { + redudp_log_errno(client, LOG_WARNING, "recv_udp_pkt"); + return; + } + + if (memcmp(&udprelayaddr, &client->udprelayaddr, sizeof(udprelayaddr)) != 0) { + char buf[INET6_ADDRSTRLEN]; + const char *addr = inet_ntop(udprelayaddr.sin_family, &udprelayaddr.sin_addr, buf, sizeof(buf)); + redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s:%u.", + addr ? addr : "?", ntohs(udprelayaddr.sin_port)); + 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 != client->instance->config.destaddr.sin_port || + pkt.header.ip.addr != client->instance->config.destaddr.sin_addr.s_addr) + { + char buf[INET6_ADDRSTRLEN]; + const char *addr = inet_ntop(AF_INET, &pkt.header.ip.addr, buf, sizeof(buf)); + redudp_log_error(client, LOG_NOTICE, "Socks5 server relayed packet from unexpected address %s:%u.", + addr ? addr : "?", ntohs(pkt.header.ip.port)); + return; + } + + redsocks_time(&client->last_relay_event); + redudp_bump_timeout(client); + + fwdlen = pktlen - sizeof(pkt.header); + outgoing = sendto(EVENT_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 %d bytes, but only %d 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; + char buf[0xFFFF]; // UDP packet can't be larger then that + ssize_t pktlen; + redudp_client *tmp, *client = NULL; + + assert(fd == EVENT_FD(&self->listener)); + pktlen = recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr); + if (pktlen == -1) { + return; + } + + // TODO: this lookup may be SLOOOOOW. + list_for_each_entry(tmp, &self->clients, list) { + 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, 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.destaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + 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) ? &instance->config.bindaddr.sin_addr : + (strcmp(entry->key, "local_port") == 0) ? &instance->config.bindaddr.sin_port : + (strcmp(entry->key, "ip") == 0) ? &instance->config.relayaddr.sin_addr : + (strcmp(entry->key, "port") == 0) ? &instance->config.relayaddr.sin_port : + (strcmp(entry->key, "login") == 0) ? &instance->config.login : + (strcmp(entry->key, "password") == 0) ? &instance->config.password : + (strcmp(entry->key, "dest_ip") == 0) ? &instance->config.destaddr.sin_addr : + (strcmp(entry->key, "dest_port") == 0) ? &instance->config.destaddr.sin_port : + (strcmp(entry->key, "max_pktqueue") == 0) ? &instance->config.max_pktqueue : + (strcmp(entry->key, "udp_timeout") == 0) ? &instance->config.udp_timeout: + (strcmp(entry->key, "udp_timeout_stream") == 0) ? &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; + } + + 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; + } + fd = -1; + + return 0; + +fail: + redudp_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 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"); + if (close(EVENT_FD(&instance->listener)) != 0) + log_errno(LOG_WARNING, "close"); + 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={,}: */ diff --git a/redudp.h b/redudp.h new file mode 100644 index 0000000..308bd33 --- /dev/null +++ b/redudp.h @@ -0,0 +1,47 @@ +#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 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 */