mirror of
https://github.com/darkk/redsocks.git
synced 2025-08-29 13:15:30 +00:00
merge commits from upstream
This commit is contained in:
commit
9d876181b3
2
Makefile
2
Makefile
@ -3,7 +3,7 @@ CFLAGS=-std=gnu99 -Wall -g -O0
|
|||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: redsocks
|
all: redsocks
|
||||||
|
|
||||||
obj = 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
|
obj = 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
|
||||||
src = $(patsubst %.o,%.c,$(obj))
|
src = $(patsubst %.o,%.c,$(obj))
|
||||||
|
|
||||||
redsocks: $(obj)
|
redsocks: $(obj)
|
||||||
|
2
README
2
README
@ -32,6 +32,8 @@ Login/password authentication is supported for SOCKS5/HTTPS connections.
|
|||||||
SOCKS4 supports only username, password is ignored. for HTTPS, currently
|
SOCKS4 supports only username, password is ignored. for HTTPS, currently
|
||||||
only Basic and Digest scheme is supported.
|
only Basic and Digest scheme is supported.
|
||||||
|
|
||||||
|
Redirect UDP packets via SOCKS5 proxy server.
|
||||||
|
|
||||||
Redirect any HTTP connection to proxy that does not support transparent
|
Redirect any HTTP connection to proxy that does not support transparent
|
||||||
proxying (e.g. old SQUID had broken `acl myport' for such connections).
|
proxying (e.g. old SQUID had broken `acl myport' for such connections).
|
||||||
|
|
||||||
|
4
main.c
4
main.c
@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
extern app_subsys redsocks_subsys;
|
extern app_subsys redsocks_subsys;
|
||||||
extern app_subsys base_subsys;
|
extern app_subsys base_subsys;
|
||||||
// extern app_subsys reddns_subsys;
|
extern app_subsys redudp_subsys;
|
||||||
|
|
||||||
app_subsys *subsystems[] = {
|
app_subsys *subsystems[] = {
|
||||||
&redsocks_subsys,
|
&redsocks_subsys,
|
||||||
&base_subsys,
|
&base_subsys,
|
||||||
// &reddns_subsys,
|
&redudp_subsys,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *confname = "redsocks.conf";
|
static const char *confname = "redsocks.conf";
|
||||||
|
160
redsocks.c
160
redsocks.c
@ -19,7 +19,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -41,26 +40,6 @@
|
|||||||
static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffev, int how);
|
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_connect_subsys;
|
||||||
extern relay_subsys http_relay_subsys;
|
extern relay_subsys http_relay_subsys;
|
||||||
extern relay_subsys socks4_subsys;
|
extern relay_subsys socks4_subsys;
|
||||||
@ -73,7 +52,7 @@ static relay_subsys *relay_subsystems[] =
|
|||||||
&socks5_subsys,
|
&socks5_subsys,
|
||||||
};
|
};
|
||||||
|
|
||||||
list_head instances = LIST_HEAD_INIT(instances);
|
static list_head instances = LIST_HEAD_INIT(instances);
|
||||||
|
|
||||||
static parser_entry redsocks_entries[] =
|
static parser_entry redsocks_entries[] =
|
||||||
{
|
{
|
||||||
@ -169,9 +148,10 @@ static parser_section redsocks_conf_section =
|
|||||||
.onexit = redsocks_onexit
|
.onexit = redsocks_onexit
|
||||||
};
|
};
|
||||||
|
|
||||||
void redsocks_log_write(
|
void redsocks_log_write_plain(
|
||||||
const char *file, int line, const char *func, int do_errno,
|
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;
|
int saved_errno = errno;
|
||||||
struct evbuffer *fmt = evbuffer_new();
|
struct evbuffer *fmt = evbuffer_new();
|
||||||
@ -183,15 +163,15 @@ void redsocks_log_write(
|
|||||||
// no return, as I have to call va_start/va_end
|
// 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));
|
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));
|
strncpy(destaddr_str, "???", sizeof(destaddr_str));
|
||||||
|
|
||||||
if (fmt) {
|
if (fmt) {
|
||||||
evbuffer_add_printf(fmt, "[%s:%i->%s:%i]: %s",
|
evbuffer_add_printf(fmt, "[%s:%i->%s:%i]: %s",
|
||||||
clientaddr_str, ntohs(client->clientaddr.sin_port),
|
clientaddr_str, ntohs(clientaddr->sin_port),
|
||||||
destaddr_str, ntohs(client->destaddr.sin_port),
|
destaddr_str, ntohs(destaddr->sin_port),
|
||||||
orig_fmt);
|
orig_fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,16 +184,6 @@ void redsocks_log_write(
|
|||||||
va_end(ap);
|
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)
|
void redsocks_touch_client(redsocks_client *client)
|
||||||
{
|
{
|
||||||
redsocks_time(&client->last_event);
|
redsocks_time(&client->last_event);
|
||||||
@ -382,15 +352,9 @@ static void redsocks_shutdown(redsocks_client *client, struct bufferevent *buffe
|
|||||||
// I assume that -1 is invalid errno value
|
// I assume that -1 is invalid errno value
|
||||||
static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev)
|
static int redsocks_socket_geterrno(redsocks_client *client, struct bufferevent *buffev)
|
||||||
{
|
{
|
||||||
int error;
|
int pseudo_errno = red_socket_geterrno(buffev);
|
||||||
int pseudo_errno;
|
if (pseudo_errno == -1) {
|
||||||
size_t optlen = sizeof(pseudo_errno);
|
redsocks_log_errno(client, LOG_ERR, "red_socket_geterrno");
|
||||||
|
|
||||||
assert(EVENT_FD(&buffev->ev_read) == EVENT_FD(&buffev->ev_write));
|
|
||||||
|
|
||||||
error = getsockopt(EVENT_FD(&buffev->ev_read), SOL_SOCKET, SO_ERROR, &pseudo_errno, (socklen_t*)&optlen);
|
|
||||||
if (error) {
|
|
||||||
redsocks_log_errno(client, LOG_ERR, "getsockopt");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return pseudo_errno;
|
return pseudo_errno;
|
||||||
@ -417,14 +381,9 @@ static void redsocks_event_error(struct bufferevent *buffev, short what, void *_
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
errno = redsocks_socket_geterrno(client, buffev);
|
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",
|
buffev == client->relay ? "relay" : "client",
|
||||||
what & EVBUFFER_READ ? "EVBUFFER_READ" : "0",
|
event_fmt(what));
|
||||||
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);
|
|
||||||
redsocks_drop_client(client);
|
redsocks_drop_client(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -478,27 +437,41 @@ fail:
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redsocks_write_helper_ex(
|
int redsocks_write_helper_ex(
|
||||||
struct bufferevent *buffev, redsocks_client *client,
|
struct bufferevent *buffev, redsocks_client *client,
|
||||||
redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high)
|
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;
|
int len;
|
||||||
struct evbuffer *buff = NULL;
|
struct evbuffer *buff = NULL;
|
||||||
int drop = 1;
|
int drop = 1;
|
||||||
|
|
||||||
if (mkmessage) {
|
if (mkmessage) {
|
||||||
buff = mkmessage(client);
|
buff = mkmessage(p);
|
||||||
if (!buff)
|
if (!buff)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
len = bufferevent_write_buffer(client->relay, buff);
|
assert(!client || buffev == client->relay);
|
||||||
|
len = bufferevent_write_buffer(buffev, buff);
|
||||||
if (len < 0) {
|
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;
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client->state = state;
|
if (client)
|
||||||
|
client->state = state;
|
||||||
buffev->wm_read.low = wm_low;
|
buffev->wm_read.low = wm_low;
|
||||||
buffev->wm_read.high = wm_high;
|
buffev->wm_read.high = wm_high;
|
||||||
bufferevent_enable(buffev, EV_READ);
|
bufferevent_enable(buffev, EV_READ);
|
||||||
@ -507,35 +480,29 @@ void redsocks_write_helper_ex(
|
|||||||
fail:
|
fail:
|
||||||
if (buff)
|
if (buff)
|
||||||
evbuffer_free(buff);
|
evbuffer_free(buff);
|
||||||
if (drop)
|
if (drop && client)
|
||||||
redsocks_drop_client(client);
|
redsocks_drop_client(client);
|
||||||
|
return drop ? -1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redsocks_write_helper(
|
int redsocks_write_helper(
|
||||||
struct bufferevent *buffev, redsocks_client *client,
|
struct bufferevent *buffev, redsocks_client *client,
|
||||||
redsocks_message_maker mkmessage, int state, size_t wm_only)
|
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)
|
static void redsocks_relay_connected(struct bufferevent *buffev, void *_arg)
|
||||||
{
|
{
|
||||||
redsocks_client *client = _arg;
|
redsocks_client *client = _arg;
|
||||||
int pseudo_errno;
|
|
||||||
|
|
||||||
assert(buffev == client->relay);
|
assert(buffev == client->relay);
|
||||||
|
|
||||||
redsocks_touch_client(client);
|
redsocks_touch_client(client);
|
||||||
|
|
||||||
pseudo_errno = redsocks_socket_geterrno(client, buffev);
|
if (!red_is_socket_connected_ok(buffev)) {
|
||||||
if (pseudo_errno == -1) {
|
redsocks_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok");
|
||||||
redsocks_log_errno(client, LOG_NOTICE, "redsocks_socket_geterrno");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pseudo_errno) {
|
|
||||||
errno = pseudo_errno;
|
|
||||||
redsocks_log_errno(client, LOG_NOTICE, "connect");
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,53 +517,12 @@ fail:
|
|||||||
|
|
||||||
void redsocks_connect_relay(redsocks_client *client)
|
void redsocks_connect_relay(redsocks_client *client)
|
||||||
{
|
{
|
||||||
int on = 1;
|
client->relay = red_connect_relay(&client->instance->config.relayaddr,
|
||||||
int relay_fd = -1;
|
redsocks_relay_connected, redsocks_event_error, client);
|
||||||
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);
|
|
||||||
if (!client->relay) {
|
if (!client->relay) {
|
||||||
redsocks_log_errno(client, LOG_ERR, "bufferevent_new");
|
redsocks_log_errno(client, LOG_ERR, "red_connect_relay");
|
||||||
goto fail;
|
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)
|
static void redsocks_accept_client(int fd, short what, void *_arg)
|
||||||
|
@ -53,4 +53,29 @@ redsocks {
|
|||||||
// password = "baz";
|
// 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.
|
||||||
|
30
redsocks.h
30
redsocks.h
@ -6,12 +6,6 @@
|
|||||||
#include <event.h>
|
#include <event.h>
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
|
|
||||||
#if defined __GNUC__
|
|
||||||
#define PACKED __attribute__((packed))
|
|
||||||
#else
|
|
||||||
#error Unknown compiler, modify types.h for it
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
struct redsocks_client_t;
|
struct redsocks_client_t;
|
||||||
struct redsocks_instance_t;
|
struct redsocks_instance_t;
|
||||||
@ -73,18 +67,32 @@ 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);
|
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)(redsocks_client *client);
|
||||||
|
typedef struct evbuffer* (*redsocks_message_maker_plain)(void *p);
|
||||||
struct evbuffer *mkevbuffer(void *data, size_t len);
|
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,
|
struct bufferevent *buffev, redsocks_client *client,
|
||||||
redsocks_message_maker mkmessage, int state, size_t wm_low, size_t wm_high);
|
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,
|
struct bufferevent *buffev, redsocks_client *client,
|
||||||
redsocks_message_maker mkmessage, int state, size_t wm_only);
|
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_error(client, prio, msg...) \
|
||||||
#define redsocks_log_errno(client, prio, msg...) redsocks_log_write(__FILE__, __LINE__, __func__, 1, client, prio, ## msg)
|
redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, &(client)->destaddr, 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_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 tabstop=4 softtabstop=4 shiftwidth=4: */
|
||||||
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */
|
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */
|
||||||
|
749
redudp.c
Normal file
749
redudp.c
Normal file
@ -0,0 +1,749 @@
|
|||||||
|
/* redsocks - transparent TCP-to-proxy redirector
|
||||||
|
* Copyright (C) 2007-2008 Leonid Evdokimov <leon@darkk.net.ru>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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, "<trace>");
|
||||||
|
|
||||||
|
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, "<trace>");
|
||||||
|
|
||||||
|
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, "<trace>");
|
||||||
|
|
||||||
|
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, "<trace>");
|
||||||
|
|
||||||
|
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, "<trace>");
|
||||||
|
|
||||||
|
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={,}: */
|
47
redudp.h
Normal file
47
redudp.h
Normal file
@ -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 */
|
174
socks5.c
174
socks5.c
@ -21,6 +21,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "redsocks.h"
|
#include "redsocks.h"
|
||||||
|
#include "socks5.h"
|
||||||
|
|
||||||
typedef enum socks5_state_t {
|
typedef enum socks5_state_t {
|
||||||
socks5_new,
|
socks5_new,
|
||||||
@ -37,82 +38,6 @@ typedef struct socks5_client_t {
|
|||||||
int to_skip; // valid while reading last reply (after main request)
|
int to_skip; // valid while reading last reply (after main request)
|
||||||
} socks5_client;
|
} 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[] = {
|
const char *socks5_strstatus[] = {
|
||||||
"ok",
|
"ok",
|
||||||
"server failure",
|
"server failure",
|
||||||
@ -124,6 +49,32 @@ const char *socks5_strstatus[] = {
|
|||||||
"command not supported",
|
"command not supported",
|
||||||
"address type 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)
|
||||||
|
{
|
||||||
|
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)
|
void socks5_client_init(redsocks_client *client)
|
||||||
{
|
{
|
||||||
@ -131,27 +82,25 @@ void socks5_client_init(redsocks_client *client)
|
|||||||
const redsocks_config *config = &client->instance->config;
|
const redsocks_config *config = &client->instance->config;
|
||||||
|
|
||||||
client->state = socks5_new;
|
client->state = socks5_new;
|
||||||
socks5->do_password = 0;
|
socks5->do_password = socks5_is_valid_cred(config->login, config->password);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct evbuffer *socks5_mkmethods(redsocks_client *client)
|
static struct evbuffer *socks5_mkmethods(redsocks_client *client)
|
||||||
{
|
{
|
||||||
socks5_client *socks5 = (void*)(client + 1);
|
socks5_client *socks5 = (void*)(client + 1);
|
||||||
int len = sizeof(socks5_method_req) + socks5->do_password;
|
return socks5_mkmethods_plain(socks5->do_password);
|
||||||
socks5_method_req *req = calloc(1, len);
|
}
|
||||||
|
|
||||||
|
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->ver = socks5_ver;
|
||||||
req->num_methods = 1 + socks5->do_password;
|
req->num_methods = 1 + do_password;
|
||||||
req->methods[0] = socks5_auth_none;
|
req->methods[0] = socks5_auth_none;
|
||||||
if (socks5->do_password)
|
if (do_password)
|
||||||
req->methods[1] = socks5_auth_password;
|
req->methods[1] = socks5_auth_password;
|
||||||
|
|
||||||
struct evbuffer *ret = mkevbuffer(req, len);
|
struct evbuffer *ret = mkevbuffer(req, len);
|
||||||
@ -161,8 +110,11 @@ static struct evbuffer *socks5_mkmethods(redsocks_client *client)
|
|||||||
|
|
||||||
static struct evbuffer *socks5_mkpassword(redsocks_client *client)
|
static struct evbuffer *socks5_mkpassword(redsocks_client *client)
|
||||||
{
|
{
|
||||||
const char *login = client->instance->config.login;
|
return socks5_mkpassword_plain(client->instance->config.login, client->instance->config.password);
|
||||||
const char *password = client->instance->config.password;
|
}
|
||||||
|
|
||||||
|
struct evbuffer *socks5_mkpassword_plain(const char *login, const char *password)
|
||||||
|
{
|
||||||
size_t ulen = strlen(login);
|
size_t ulen = strlen(login);
|
||||||
size_t plen = strlen(password);
|
size_t plen = strlen(password);
|
||||||
size_t length = 1 /* version */ + 1 + ulen + 1 + plen;
|
size_t length = 1 /* version */ + 1 + ulen + 1 + plen;
|
||||||
@ -176,22 +128,29 @@ static struct evbuffer *socks5_mkpassword(redsocks_client *client)
|
|||||||
return mkevbuffer(req, length);
|
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 {
|
struct {
|
||||||
socks5_req head;
|
socks5_req head;
|
||||||
socks5_addr_ipv4 ip;
|
socks5_addr_ipv4 ip;
|
||||||
} PACKED req;
|
} PACKED req;
|
||||||
|
|
||||||
|
assert(destaddr->sin_family == AF_INET);
|
||||||
|
|
||||||
req.head.ver = socks5_ver;
|
req.head.ver = socks5_ver;
|
||||||
req.head.cmd = socks5_cmd_connect;
|
req.head.cmd = socks5_cmd;
|
||||||
req.head.reserved = 0;
|
req.head.reserved = 0;
|
||||||
req.head.addrtype = socks5_addrtype_ipv4;
|
req.head.addrtype = socks5_addrtype_ipv4;
|
||||||
req.ip.addr = client->destaddr.sin_addr.s_addr;
|
req.ip.addr = destaddr->sin_addr.s_addr;
|
||||||
req.ip.port = client->destaddr.sin_port;
|
req.ip.port = destaddr->sin_port;
|
||||||
return mkevbuffer(&req, sizeof(req));
|
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)
|
static void socks5_write_cb(struct bufferevent *buffev, void *_arg)
|
||||||
{
|
{
|
||||||
redsocks_client *client = _arg;
|
redsocks_client *client = _arg;
|
||||||
@ -206,15 +165,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)
|
static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5)
|
||||||
{
|
{
|
||||||
socks5_method_reply reply;
|
socks5_method_reply reply;
|
||||||
|
const char *error = NULL;
|
||||||
|
|
||||||
if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0)
|
if (redsocks_read_expected(client, buffev->input, &reply, sizes_equal, sizeof(reply)) < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (reply.ver != socks5_ver) {
|
error = socks5_is_known_auth_method(&reply, socks5->do_password);
|
||||||
redsocks_log_error(client, LOG_NOTICE, "Socks5 server reported unexpected auth methods reply version...");
|
if (error) {
|
||||||
|
redsocks_log_error(client, LOG_NOTICE, "socks5_is_known_auth_method: %s", error);
|
||||||
redsocks_drop_client(client);
|
redsocks_drop_client(client);
|
||||||
}
|
}
|
||||||
else if (reply.method == socks5_auth_none) {
|
else if (reply.method == socks5_auth_none) {
|
||||||
@ -223,17 +196,12 @@ static void socks5_read_auth_methods(struct bufferevent *buffev, redsocks_client
|
|||||||
socks5_mkconnect, socks5_request_sent, sizeof(socks5_reply)
|
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(
|
redsocks_write_helper(
|
||||||
buffev, client,
|
buffev, client,
|
||||||
socks5_mkpassword, socks5_auth_sent, sizeof(socks5_auth_reply)
|
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)
|
static void socks5_read_auth_reply(struct bufferevent *buffev, redsocks_client *client, socks5_client *socks5)
|
||||||
|
104
socks5.h
Normal file
104
socks5.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#ifndef SOCKS5_H
|
||||||
|
#define SOCKS5_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#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={,}: */
|
||||||
|
#endif /* SOCKS5_H */
|
123
utils.c
Normal file
123
utils.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: */
|
25
utils.h
25
utils.h
@ -2,12 +2,21 @@
|
|||||||
#define UTILS_H_SAT_FEB__2_02_24_05_2008
|
#define UTILS_H_SAT_FEB__2_02_24_05_2008
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <event.h>
|
||||||
|
|
||||||
|
|
||||||
#define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0]))
|
#define SIZEOF_ARRAY(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||||
#define FOREACH(ptr, array) for (ptr = array; ptr < array + SIZEOF_ARRAY(array); ptr++)
|
#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 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
|
* container_of - cast a member of a structure out to the containing structure
|
||||||
@ -22,6 +31,22 @@
|
|||||||
|
|
||||||
#define free_null(p) if (!(p)) ; else free (p)
|
#define free_null(p) if (!(p)) ; else free (p)
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#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 tabstop=4 softtabstop=4 shiftwidth=4: */
|
||||||
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */
|
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */
|
||||||
#endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */
|
#endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */
|
||||||
|
Loading…
Reference in New Issue
Block a user