Redsocks is a daemon running on the local system, that will transparently tunnel any TCP connection via a remote SOCKS4, SOCKS5 or HTTP proxy server. It uses the system firewall's redirection facility to intercept TCP connections, thus the redirection is system-wide, with fine-grained control, and does not depend on LD_PRELOAD libraries. Signed-off-by: Johannes Morgenroth <jm@m-network.de>lilik-openwrt-22.03
@ -0,0 +1,56 @@ | |||
# | |||
# Copyright (C) 2014 OpenWrt.org | |||
# | |||
# This is free software, licensed under the GNU General Public License v2. | |||
# See /LICENSE for more information. | |||
# | |||
include $(TOPDIR)/rules.mk | |||
PKG_NAME:=redsocks | |||
PKG_VERSION:=0.4 | |||
PKG_RELEASE:=1 | |||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-release-$(PKG_VERSION) | |||
PKG_SOURCE:=release-$(PKG_VERSION).tar.gz | |||
PKG_SOURCE_URL:=https://github.com/darkk/redsocks/archive/ | |||
PKG_MD5SUM:=810102ef06a9ea796f310ae811afc6a8 | |||
PKG_MAINTAINER:=Johannes Morgenroth <jm@m-network.de> | |||
PKG_LICENSE:=Apache-2.0 | |||
include $(INCLUDE_DIR)/package.mk | |||
define Package/redsocks | |||
SECTION:=net | |||
CATEGORY:=Network | |||
DEPENDS:=+libevent2 | |||
TITLE:=Redirect any TCP connection to a SOCKS or HTTPS proxy server | |||
endef | |||
define Package/redsocks/conffiles | |||
/etc/redsocks.conf | |||
endef | |||
define Package/redsocks/description | |||
Redsocks is a daemon running on the local system, that will transparently | |||
tunnel any TCP connection via a remote SOCKS4, SOCKS5 or HTTP proxy server. It | |||
uses the system firewall's redirection facility to intercept TCP connections, | |||
thus the redirection is system-wide, with fine-grained control, and does | |||
not depend on LD_PRELOAD libraries. | |||
Redsocks supports tunneling TCP connections and UDP packets. It has | |||
authentication support for both, SOCKS and HTTP proxies. | |||
Also included is a small DNS server returning answers with the "truncated" flag | |||
set for any UDP query, forcing the resolver to use TCP. | |||
endef | |||
define Package/redsocks/install | |||
$(INSTALL_DIR) $(1)/usr/sbin/ | |||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/redsocks $(1)/usr/sbin/ | |||
$(INSTALL_DIR) $(1)/etc/init.d/ | |||
$(INSTALL_BIN) files/redsocks.init $(1)/etc/init.d/redsocks | |||
$(INSTALL_CONF) files/redsocks.conf $(1)/etc/ | |||
endef | |||
$(eval $(call BuildPackage,redsocks)) |
@ -0,0 +1,108 @@ | |||
base { | |||
// debug: connection progress & client list on SIGUSR1 | |||
log_debug = off; | |||
// info: start and end of client session | |||
log_info = on; | |||
/* possible `log' values are: | |||
* stderr | |||
* "file:/path/to/file" | |||
* syslog:FACILITY facility is any of "daemon", "local0"..."local7" | |||
*/ | |||
// log = stderr; | |||
// log = "file:/path/to/file"; | |||
log = "syslog:local7"; | |||
// detach from console | |||
daemon = on; | |||
/* Change uid, gid and root directory, these options require root | |||
* privilegies on startup. | |||
* Note, your chroot may requre /etc/localtime if you write log to syslog. | |||
* Log is opened before chroot & uid changing. | |||
*/ | |||
// user = nobody; | |||
// group = nobody; | |||
// chroot = "/var/chroot"; | |||
/* possible `redirector' values are: | |||
* iptables - for Linux | |||
* ipf - for FreeBSD | |||
* pf - for OpenBSD | |||
* generic - some generic redirector that MAY work | |||
*/ | |||
redirector = iptables; | |||
} | |||
redsocks { | |||
/* `local_ip' defaults to 127.0.0.1 for security reasons, | |||
* use 0.0.0.0 if you want to listen on every interface. | |||
* `local_*' are used as port to redirect to. | |||
*/ | |||
local_ip = 127.0.0.1; | |||
local_port = 12345; | |||
// listen() queue length. Default value is SOMAXCONN and it should be | |||
// good enough for most of us. | |||
// listenq = 128; // SOMAXCONN equals 128 on my Linux box. | |||
// `max_accept_backoff` is a delay to retry `accept()` after accept | |||
// failure (e.g. due to lack of file descriptors). It's measured in | |||
// milliseconds and maximal value is 65535. `min_accept_backoff` is | |||
// used as initial backoff value and as a damper for `accept() after | |||
// close()` logic. | |||
// min_accept_backoff = 100; | |||
// max_accept_backoff = 60000; | |||
// `ip' and `port' are IP and tcp-port of proxy-server | |||
// You can also use hostname instead of IP, only one (random) | |||
// address of multihomed host will be used. | |||
ip = example.org; | |||
port = 1080; | |||
// known types: socks4, socks5, http-connect, http-relay | |||
type = socks5; | |||
// login = "foobar"; | |||
// password = "baz"; | |||
} | |||
redudp { | |||
// `local_ip' should not be 0.0.0.0 as it's also used for outgoing | |||
// packets that are sent as replies - and it should be fixed | |||
// if we want NAT to work properly. | |||
local_ip = 127.0.0.1; | |||
local_port = 10053; | |||
// `ip' and `port' of socks5 proxy server. | |||
ip = 10.0.0.1; | |||
port = 1080; | |||
login = username; | |||
password = pazzw0rd; | |||
// redsocks knows about two options while redirecting UDP packets at | |||
// linux: TPROXY and REDIRECT. TPROXY requires more complex routing | |||
// configuration and fresh kernel (>= 2.6.37 according to squid | |||
// developers[1]) but has hack-free way to get original destination | |||
// address, REDIRECT is easier to configure, but requires `dest_ip` and | |||
// `dest_port` to be set, limiting packet redirection to single | |||
// destination. | |||
// [1] http://wiki.squid-cache.org/Features/Tproxy4 | |||
dest_ip = 8.8.8.8; | |||
dest_port = 53; | |||
udp_timeout = 30; | |||
udp_timeout_stream = 180; | |||
} | |||
dnstc { | |||
// fake and really dumb DNS server that returns "truncated answer" to | |||
// every query via UDP, RFC-compliant resolver should repeat same query | |||
// via TCP in this case. | |||
local_ip = 127.0.0.1; | |||
local_port = 5300; | |||
} | |||
// you can add more `redsocks' and `redudp' sections if you need. |
@ -0,0 +1,36 @@ | |||
#!/bin/sh /etc/rc.common | |||
# Copyright (C) 2007 OpenWrt.org | |||
START=90 | |||
# check if configuration exists | |||
[ -e "/etc/redsocks.conf" ] || exit 0 | |||
start() { | |||
if [ -e "/var/run/redsocks.pid" ]; then | |||
echo "redsocks is already running" | |||
exit 0 | |||
fi | |||
/bin/echo -n "running redsocks ..." | |||
# startup the safety-wrapper for the daemon | |||
/usr/sbin/redsocks -p /var/run/redsocks.pid | |||
/bin/echo " done" | |||
} | |||
stop() { | |||
if [ ! -e "/var/run/redsocks.pid" ]; then | |||
echo "redsocks is not running" | |||
exit 0 | |||
fi | |||
/bin/echo -n "stopping redsocks ..." | |||
# kill the process | |||
/bin/kill $(cat /var/run/redsocks.pid) | |||
rm /var/run/redsocks.pid | |||
echo " done" | |||
} |
@ -0,0 +1,53 @@ | |||
From 290f19972e9f7b74f818ae211cb535e32f1f314f Mon Sep 17 00:00:00 2001 | |||
From: Leonid Evdokimov <leon@darkk.net.ru> | |||
Date: Tue, 10 Apr 2012 00:57:26 +0400 | |||
Subject: [PATCH 01/12] Fix bug in DNS resolution - results were ignored (since | |||
8179a1ff). | |||
--- | |||
parser.c | 10 +++++----- | |||
1 file changed, 5 insertions(+), 5 deletions(-) | |||
diff --git a/parser.c b/parser.c | |||
index 85d3533..6198828 100644 | |||
--- a/parser.c | |||
+++ b/parser.c | |||
@@ -295,22 +295,22 @@ static int vp_in_addr(parser_context *context, void *addr, const char *token) | |||
memcpy(addr, &ia, sizeof(ia)); | |||
} | |||
else { | |||
- struct addrinfo *addr, hints; | |||
+ struct addrinfo *ainfo, hints; | |||
int err; | |||
memset(&hints, 0, sizeof(hints)); | |||
hints.ai_family = AF_INET; /* IPv4-only */ | |||
hints.ai_socktype = SOCK_STREAM; /* I want to have one address once and ONLY once, that's why I specify socktype and protocol */ | |||
hints.ai_protocol = IPPROTO_TCP; | |||
hints.ai_flags = AI_ADDRCONFIG; /* I don't need IPv4 addrs without IPv4 connectivity */ | |||
- err = getaddrinfo(token, NULL, &hints, &addr); | |||
+ err = getaddrinfo(token, NULL, &hints, &ainfo); | |||
if (err == 0) { | |||
int count, taken; | |||
struct addrinfo *iter; | |||
struct sockaddr_in *resolved_addr; | |||
- for (iter = addr, count = 0; iter; iter = iter->ai_next, ++count) | |||
+ for (iter = ainfo, count = 0; iter; iter = iter->ai_next, ++count) | |||
; | |||
taken = rand() % count; | |||
- for (iter = addr; taken > 0; iter = iter->ai_next, --taken) | |||
+ for (iter = ainfo; taken > 0; iter = iter->ai_next, --taken) | |||
; | |||
resolved_addr = (struct sockaddr_in*)iter->ai_addr; | |||
assert(resolved_addr->sin_family == iter->ai_family && iter->ai_family == AF_INET); | |||
@@ -318,7 +318,7 @@ static int vp_in_addr(parser_context *context, void *addr, const char *token) | |||
log_error(LOG_WARNING, "%s resolves to %d addresses, using %s", | |||
token, count, inet_ntoa(resolved_addr->sin_addr)); | |||
memcpy(addr, &resolved_addr->sin_addr, sizeof(ia)); | |||
- freeaddrinfo(addr); | |||
+ freeaddrinfo(ainfo); | |||
} | |||
else { | |||
if (err == EAI_SYSTEM) | |||
-- | |||
1.9.1 | |||
@ -0,0 +1,161 @@ | |||
From 6015b3a6f26e04dd5d78cd6c1320886fc9035612 Mon Sep 17 00:00:00 2001 | |||
From: Leonid Evdokimov <leon@darkk.net.ru> | |||
Date: Tue, 10 Apr 2012 01:37:34 +0400 | |||
Subject: [PATCH 02/12] inet_ntop -> red_inet_ntop | |||
--- | |||
redsocks.c | 13 ++++--------- | |||
redudp.c | 19 +++++++++++-------- | |||
utils.c | 37 +++++++++++++++++++++++++++++++++---- | |||
utils.h | 7 +++++++ | |||
4 files changed, 55 insertions(+), 21 deletions(-) | |||
diff --git a/redsocks.c b/redsocks.c | |||
index d085e10..ba5eab2 100644 | |||
--- a/redsocks.c | |||
+++ b/redsocks.c | |||
@@ -207,22 +207,17 @@ void redsocks_log_write_plain( | |||
int saved_errno = errno; | |||
struct evbuffer *fmt = evbuffer_new(); | |||
va_list ap; | |||
- char clientaddr_str[INET6_ADDRSTRLEN], destaddr_str[INET6_ADDRSTRLEN]; | |||
+ char clientaddr_str[RED_INET_ADDRSTRLEN], destaddr_str[RED_INET_ADDRSTRLEN]; | |||
if (!fmt) { | |||
log_errno(LOG_ERR, "evbuffer_new()"); | |||
// no return, as I have to call va_start/va_end | |||
} | |||
- if (!inet_ntop(clientaddr->sin_family, &clientaddr->sin_addr, clientaddr_str, sizeof(clientaddr_str))) | |||
- strncpy(clientaddr_str, "???", sizeof(clientaddr_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(clientaddr->sin_port), | |||
- destaddr_str, ntohs(destaddr->sin_port), | |||
+ evbuffer_add_printf(fmt, "[%s->%s]: %s", | |||
+ red_inet_ntop(clientaddr, clientaddr_str, sizeof(clientaddr_str)), | |||
+ red_inet_ntop(destaddr, destaddr_str, sizeof(destaddr_str)), | |||
orig_fmt); | |||
} | |||
diff --git a/redudp.c b/redudp.c | |||
index 0a97852..9516a50 100644 | |||
--- a/redudp.c | |||
+++ b/redudp.c | |||
@@ -436,10 +436,9 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
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)); | |||
+ char buf[RED_INET_ADDRSTRLEN]; | |||
+ redudp_log_error(client, LOG_NOTICE, "Got packet from unexpected address %s.", | |||
+ red_inet_ntop(&udprelayaddr, buf, sizeof(buf))); | |||
return; | |||
} | |||
@@ -459,10 +458,14 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
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)); | |||
+ char buf[RED_INET_ADDRSTRLEN]; | |||
+ struct sockaddr_in pktaddr = { | |||
+ .sin_family = AF_INET, | |||
+ .sin_addr = { pkt.header.ip.addr }, | |||
+ .sin_port = pkt.header.ip.port, | |||
+ }; | |||
+ redudp_log_error(client, LOG_NOTICE, "Socks5 server relayed packet from unexpected address %s.", | |||
+ red_inet_ntop(&pktaddr, buf, sizeof(buf))); | |||
return; | |||
} | |||
diff --git a/utils.c b/utils.c | |||
index c6ced51..6e1f3af 100644 | |||
--- a/utils.c | |||
+++ b/utils.c | |||
@@ -18,6 +18,7 @@ | |||
#include <errno.h> | |||
#include <assert.h> | |||
#include <fcntl.h> | |||
+#include <string.h> | |||
#include <sys/socket.h> | |||
#include <netinet/in.h> | |||
#include <arpa/inet.h> | |||
@@ -42,10 +43,9 @@ int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inadd | |||
} | |||
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 %zd from %s:%u! impossible! dropping it...", | |||
- pktlen, addr ? addr : "?", ntohs(inaddr->sin_port)); | |||
+ char buf[RED_INET_ADDRSTRLEN]; | |||
+ log_error(LOG_WARNING, "wow! Truncated udp packet of size %zd from %s! impossible! dropping it...", | |||
+ pktlen, red_inet_ntop(inaddr, buf, sizeof(buf))); | |||
return -1; | |||
} | |||
@@ -176,4 +176,33 @@ int red_is_socket_connected_ok(struct bufferevent *buffev) | |||
} | |||
} | |||
+char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size) | |||
+{ | |||
+ const char *retval = 0; | |||
+ size_t len = 0; | |||
+ uint16_t port; | |||
+ const char placeholder[] = "???:???"; | |||
+ | |||
+ assert(buffer_size >= sizeof(placeholder)); | |||
+ | |||
+ memset(buffer, buffer_size, 0); | |||
+ if (sa->sin_family == AF_INET) { | |||
+ retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size); | |||
+ port = ((struct sockaddr_in*)sa)->sin_port; | |||
+ } | |||
+ else if (sa->sin_family == AF_INET6) { | |||
+ retval = inet_ntop(AF_INET6, &((const struct sockaddr_in6*)sa)->sin6_addr, buffer, buffer_size); | |||
+ port = ((struct sockaddr_in6*)sa)->sin6_port; | |||
+ } | |||
+ if (retval) { | |||
+ assert(retval == buffer); | |||
+ len = strlen(retval); | |||
+ snprintf(buffer + len, buffer_size - len, ":%d", ntohs(port)); | |||
+ } | |||
+ else { | |||
+ strcpy(buffer, placeholder); | |||
+ } | |||
+ return buffer; | |||
+} | |||
+ | |||
/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ | |||
diff --git a/utils.h b/utils.h | |||
index f691b77..d3af00f 100644 | |||
--- a/utils.h | |||
+++ b/utils.h | |||
@@ -57,6 +57,13 @@ int fcntl_nonblock(int fd); | |||
(what) & EVBUFFER_TIMEOUT ? "EVBUFFER_TIMEOUT" : "0", \ | |||
(what) & ~(EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF|EVBUFFER_ERROR|EVBUFFER_TIMEOUT) | |||
+#if INET6_ADDRSTRLEN < INET_ADDRSTRLEN | |||
+# error Impossible happens: INET6_ADDRSTRLEN < INET_ADDRSTRLEN | |||
+#else | |||
+# define RED_INET_ADDRSTRLEN (INET6_ADDRSTRLEN + 1 + 5 + 1) // addr + : + port + \0 | |||
+#endif | |||
+char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_size); | |||
+ | |||
/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */ | |||
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */ | |||
#endif /* UTILS_H_SAT_FEB__2_02_24_05_2008 */ | |||
-- | |||
1.9.1 | |||
@ -0,0 +1,449 @@ | |||
From 709646d59d96cb73a7e70347f37de9823e4e5f14 Mon Sep 17 00:00:00 2001 | |||
From: Leonid Evdokimov <leon@darkk.net.ru> | |||
Date: Fri, 13 Apr 2012 01:57:23 +0400 | |||
Subject: [PATCH 03/12] Initial support for UDP + TPROXY redirection. No more | |||
dest_ip in redudp. | |||
* TPROXY requires Linux 2.6.29+ (see man 7 ip[1]). | |||
* all redsocks code is running as root to bind to arbitrary port. | |||
* Non-Linux and old-Linux builds are broken at the moment. | |||
[1] http://www.kernel.org/doc/man-pages/online/pages/man7/ip.7.html | |||
--- | |||
dnstc.c | 2 +- | |||
redudp.c | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- | |||
redudp.h | 2 + | |||
utils.c | 43 +++++++++++++- | |||
utils.h | 2 +- | |||
5 files changed, 227 insertions(+), 19 deletions(-) | |||
diff --git a/dnstc.c b/dnstc.c | |||
index 43881d8..5f9fedd 100644 | |||
--- a/dnstc.c | |||
+++ b/dnstc.c | |||
@@ -68,7 +68,7 @@ static void dnstc_pkt_from_client(int fd, short what, void *_arg) | |||
ssize_t pktlen, outgoing; | |||
assert(fd == EVENT_FD(&self->listener)); | |||
- pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr); | |||
+ pktlen = red_recv_udp_pkt(fd, buf.raw, sizeof(buf), &clientaddr, NULL); | |||
if (pktlen == -1) | |||
return; | |||
diff --git a/redudp.c b/redudp.c | |||
index 9516a50..262af3e 100644 | |||
--- a/redudp.c | |||
+++ b/redudp.c | |||
@@ -15,6 +15,7 @@ | |||
*/ | |||
#include <stdlib.h> | |||
+#include <search.h> | |||
#include <string.h> | |||
#include <sys/types.h> | |||
#include <sys/uio.h> | |||
@@ -33,30 +34,157 @@ | |||
#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) | |||
+ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg) | |||
#define redudp_log_errno(client, prio, msg...) \ | |||
- redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, &(client)->instance->config.destaddr, prio, ## msg) | |||
+ redsocks_log_write_plain(__FILE__, __LINE__, __func__, 1, &(client)->clientaddr, get_destaddr(client), prio, ## msg) | |||
static void redudp_pkt_from_socks(int fd, short what, void *_arg); | |||
static void redudp_drop_client(redudp_client *client); | |||
static void redudp_fini_instance(redudp_instance *instance); | |||
static int redudp_fini(); | |||
+static int redudp_transparent(int fd); | |||
typedef struct redudp_expected_assoc_reply_t { | |||
socks5_reply h; | |||
socks5_addr_ipv4 ip; | |||
} PACKED redudp_expected_assoc_reply; | |||
+struct bound_udp4_key { | |||
+ struct in_addr sin_addr; | |||
+ uint16_t sin_port; | |||
+}; | |||
+ | |||
+struct bound_udp4 { | |||
+ struct bound_udp4_key key; | |||
+ int ref; | |||
+ int fd; | |||
+}; | |||
+ | |||
/*********************************************************************** | |||
* Helpers | |||
*/ | |||
+// TODO: separate binding to privileged process (this operation requires uid-0) | |||
+static void* root_bound_udp4 = NULL; // to avoid two binds to same IP:port | |||
+ | |||
+static int bound_udp4_cmp(const void *a, const void *b) | |||
+{ | |||
+ return memcmp(a, b, sizeof(struct bound_udp4_key)); | |||
+} | |||
+ | |||
+static void bound_udp4_mkkey(struct bound_udp4_key *key, const struct sockaddr_in *addr) | |||
+{ | |||
+ memset(key, 0, sizeof(*key)); | |||
+ key->sin_addr = addr->sin_addr; | |||
+ key->sin_port = addr->sin_port; | |||
+} | |||
+ | |||
+static int bound_udp4_get(const struct sockaddr_in *addr) | |||
+{ | |||
+ struct bound_udp4_key key; | |||
+ struct bound_udp4 *node, **pnode; | |||
+ | |||
+ bound_udp4_mkkey(&key, addr); | |||
+ // I assume, that memory allocation for lookup is awful, so I use | |||
+ // tfind/tsearch pair instead of tsearch/check-result. | |||
+ pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); | |||
+ if (pnode) { | |||
+ assert((*pnode)->ref > 0); | |||
+ (*pnode)->ref++; | |||
+ return (*pnode)->fd; | |||
+ } | |||
+ | |||
+ node = calloc(1, sizeof(*node)); | |||
+ if (!node) { | |||
+ log_errno(LOG_ERR, "calloc"); | |||
+ goto fail; | |||
+ } | |||
+ | |||
+ node->key = key; | |||
+ node->ref = 1; | |||
+ node->fd = socket(AF_INET, SOCK_DGRAM, 0); | |||
+ if (node->fd == -1) { | |||
+ log_errno(LOG_ERR, "socket"); | |||
+ goto fail; | |||
+ } | |||
+ | |||
+ if (0 != redudp_transparent(node->fd)) | |||
+ goto fail; | |||
+ | |||
+ if (0 != bind(node->fd, (struct sockaddr*)addr, sizeof(*addr))) { | |||
+ log_errno(LOG_ERR, "bind"); | |||
+ goto fail; | |||
+ } | |||
+ | |||
+ pnode = tsearch(node, &root_bound_udp4, bound_udp4_cmp); | |||
+ if (!pnode) { | |||
+ log_errno(LOG_ERR, "tsearch(%p) == %p", node, pnode); | |||
+ goto fail; | |||
+ } | |||
+ assert(node == *pnode); | |||
+ | |||
+ return node->fd; | |||
+ | |||
+fail: | |||
+ if (node) { | |||
+ if (node->fd != -1) | |||
+ redsocks_close(node->fd); | |||
+ free(node); | |||
+ } | |||
+ return -1; | |||
+} | |||
+ | |||
+static void bound_udp4_put(const struct sockaddr_in *addr) | |||
+{ | |||
+ struct bound_udp4_key key; | |||
+ struct bound_udp4 **pnode, *node; | |||
+ void *parent; | |||
+ | |||
+ bound_udp4_mkkey(&key, addr); | |||
+ pnode = tfind(&key, &root_bound_udp4, bound_udp4_cmp); | |||
+ assert(pnode && (*pnode)->ref > 0); | |||
+ | |||
+ node = *pnode; | |||
+ | |||
+ node->ref--; | |||
+ if (node->ref) | |||
+ return; | |||
+ | |||
+ parent = tdelete(node, &root_bound_udp4, bound_udp4_cmp); | |||
+ assert(parent); | |||
+ | |||
+ redsocks_close(node->fd); // expanding `pnode` to avoid use after free | |||
+ free(node); | |||
+} | |||
+ | |||
+static int redudp_transparent(int fd) | |||
+{ | |||
+ int on = 1; | |||
+ int error = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); | |||
+ if (error) | |||
+ log_errno(LOG_ERR, "setsockopt(..., SOL_IP, IP_TRANSPARENT)"); | |||
+ return error; | |||
+} | |||
+ | |||
+static int do_tproxy(redudp_instance* instance) | |||
+{ | |||
+ return instance->config.destaddr.sin_addr.s_addr == 0; | |||
+} | |||
+ | |||
+static struct sockaddr_in* get_destaddr(redudp_client *client) | |||
+{ | |||
+ if (do_tproxy(client->instance)) | |||
+ return &client->destaddr; | |||
+ else | |||
+ return &client->instance->config.destaddr; | |||
+} | |||
+ | |||
static void redudp_fill_preamble(socks5_udp_preabmle *preamble, redudp_client *client) | |||
{ | |||
preamble->reserved = 0; | |||
preamble->frag_no = 0; /* fragmentation is not supported */ | |||
preamble->addrtype = socks5_addrtype_ipv4; | |||
- preamble->ip.addr = client->instance->config.destaddr.sin_addr.s_addr; | |||
- preamble->ip.port = client->instance->config.destaddr.sin_port; | |||
+ preamble->ip.addr = get_destaddr(client)->sin_addr.s_addr; | |||
+ preamble->ip.port = get_destaddr(client)->sin_port; | |||
} | |||
static struct evbuffer* socks5_mkmethods_plain_wrapper(void *p) | |||
@@ -104,6 +232,8 @@ static void redudp_drop_client(redudp_client *client) | |||
redudp_log_errno(client, LOG_ERR, "event_del"); | |||
redsocks_close(fd); | |||
} | |||
+ if (client->sender_fd != -1) | |||
+ bound_udp4_put(&client->destaddr); | |||
list_for_each_entry_safe(q, tmp, &client->queue, list) { | |||
list_del(&q->list); | |||
free(q); | |||
@@ -344,7 +474,8 @@ 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>"); | |||
+ char relayaddr_str[RED_INET_ADDRSTRLEN]; | |||
+ redudp_log_error(client, LOG_DEBUG, "via %s", red_inet_ntop(&client->instance->config.relayaddr, relayaddr_str, sizeof(relayaddr_str))); | |||
if (!red_is_socket_connected_ok(buffev)) { | |||
redudp_log_errno(client, LOG_NOTICE, "red_is_socket_connected_ok"); | |||
@@ -382,7 +513,7 @@ static void redudp_timeout(int fd, short what, void *_arg) | |||
redudp_drop_client(client); | |||
} | |||
-static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, char *buf, size_t pktlen) | |||
+static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_in *clientaddr, struct sockaddr_in *destaddr, char *buf, size_t pktlen) | |||
{ | |||
redudp_client *client = calloc(1, sizeof(*client)); | |||
@@ -395,9 +526,13 @@ static void redudp_first_pkt_from_client(redudp_instance *self, struct sockaddr_ | |||
INIT_LIST_HEAD(&client->queue); | |||
client->instance = self; | |||
memcpy(&client->clientaddr, clientaddr, sizeof(*clientaddr)); | |||
+ if (destaddr) | |||
+ memcpy(&client->destaddr, destaddr, sizeof(client->destaddr)); | |||
evtimer_set(&client->timeout, redudp_timeout, client); | |||
// XXX: self->relay_ss->init(client); | |||
+ client->sender_fd = -1; // it's postponed until socks-server replies to avoid trivial DoS | |||
+ | |||
client->relay = red_connect_relay(&client->instance->config.relayaddr, | |||
redudp_relay_connected, redudp_relay_error, client); | |||
if (!client->relay) | |||
@@ -431,7 +566,7 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
assert(fd == EVENT_FD(&client->udprelay)); | |||
- pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr); | |||
+ pktlen = red_recv_udp_pkt(fd, pkt.buf, sizeof(pkt.buf), &udprelayaddr, NULL); | |||
if (pktlen == -1) | |||
return; | |||
@@ -455,8 +590,8 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
return; | |||
} | |||
- if (pkt.header.ip.port != client->instance->config.destaddr.sin_port || | |||
- pkt.header.ip.addr != client->instance->config.destaddr.sin_addr.s_addr) | |||
+ if (pkt.header.ip.port != get_destaddr(client)->sin_port || | |||
+ pkt.header.ip.addr != get_destaddr(client)->sin_addr.s_addr) | |||
{ | |||
char buf[RED_INET_ADDRSTRLEN]; | |||
struct sockaddr_in pktaddr = { | |||
@@ -472,8 +607,18 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
redsocks_time(&client->last_relay_event); | |||
redudp_bump_timeout(client); | |||
+ if (do_tproxy(client->instance) && client->sender_fd == -1) { | |||
+ client->sender_fd = bound_udp4_get(&client->destaddr); | |||
+ if (client->sender_fd == -1) { | |||
+ redudp_log_error(client, LOG_WARNING, "bound_udp4_get failure"); | |||
+ return; | |||
+ } | |||
+ } | |||
+ | |||
fwdlen = pktlen - sizeof(pkt.header); | |||
- outgoing = sendto(EVENT_FD(&client->instance->listener), | |||
+ outgoing = sendto(do_tproxy(client->instance) | |||
+ ? client->sender_fd | |||
+ : EVENT_FD(&client->instance->listener), | |||
pkt.buf + sizeof(pkt.header), fwdlen, 0, | |||
(struct sockaddr*)&client->clientaddr, sizeof(client->clientaddr)); | |||
if (outgoing != fwdlen) { | |||
@@ -486,18 +631,21 @@ static void redudp_pkt_from_socks(int fd, short what, void *_arg) | |||
static void redudp_pkt_from_client(int fd, short what, void *_arg) | |||
{ | |||
redudp_instance *self = _arg; | |||
- struct sockaddr_in clientaddr; | |||
+ struct sockaddr_in clientaddr, destaddr, *pdestaddr; | |||
char buf[0xFFFF]; // UDP packet can't be larger then that | |||
ssize_t pktlen; | |||
redudp_client *tmp, *client = NULL; | |||
+ pdestaddr = do_tproxy(self) ? &destaddr : NULL; | |||
+ | |||
assert(fd == EVENT_FD(&self->listener)); | |||
- pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr); | |||
+ pktlen = red_recv_udp_pkt(fd, buf, sizeof(buf), &clientaddr, pdestaddr); | |||
if (pktlen == -1) | |||
return; | |||
// TODO: this lookup may be SLOOOOOW. | |||
list_for_each_entry(tmp, &self->clients, list) { | |||
+ // TODO: check destaddr | |||
if (0 == memcmp(&clientaddr, &tmp->clientaddr, sizeof(clientaddr))) { | |||
client = tmp; | |||
break; | |||
@@ -515,7 +663,7 @@ static void redudp_pkt_from_client(int fd, short what, void *_arg) | |||
} | |||
} | |||
else { | |||
- redudp_first_pkt_from_client(self, &clientaddr, buf, pktlen); | |||
+ redudp_first_pkt_from_client(self, &clientaddr, pdestaddr, buf, pktlen); | |||
} | |||
} | |||
@@ -554,7 +702,6 @@ static int redudp_onenter(parser_section *section) | |||
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; | |||
@@ -614,6 +761,28 @@ static int redudp_init_instance(redudp_instance *instance) | |||
goto fail; | |||
} | |||
+ if (do_tproxy(instance)) { | |||
+ int on = 1; | |||
+ char buf[RED_INET_ADDRSTRLEN]; | |||
+ // iptables TPROXY target does not send packets to non-transparent sockets | |||
+ if (0 != redudp_transparent(fd)) | |||
+ goto fail; | |||
+ | |||
+ error = setsockopt(fd, SOL_IP, IP_RECVORIGDSTADDR, &on, sizeof(on)); | |||
+ if (error) { | |||
+ log_errno(LOG_ERR, "setsockopt(listener, SOL_IP, IP_RECVORIGDSTADDR)"); | |||
+ goto fail; | |||
+ } | |||
+ | |||
+ log_error(LOG_DEBUG, "redudp @ %s: TPROXY", red_inet_ntop(&instance->config.bindaddr, buf, sizeof(buf))); | |||
+ } | |||
+ else { | |||
+ char buf1[RED_INET_ADDRSTRLEN], buf2[RED_INET_ADDRSTRLEN]; | |||
+ log_error(LOG_DEBUG, "redudp @ %s: destaddr=%s", | |||
+ red_inet_ntop(&instance->config.bindaddr, buf1, sizeof(buf1)), | |||
+ red_inet_ntop(&instance->config.destaddr, buf2, sizeof(buf2))); | |||
+ } | |||
+ | |||
error = bind(fd, (struct sockaddr*)&instance->config.bindaddr, sizeof(instance->config.bindaddr)); | |||
if (error) { | |||
log_errno(LOG_ERR, "bind"); | |||
diff --git a/redudp.h b/redudp.h | |||
index 308bd33..3f1d9d1 100644 | |||
--- a/redudp.h | |||
+++ b/redudp.h | |||
@@ -24,6 +24,8 @@ typedef struct redudp_client_t { | |||
list_head list; | |||
redudp_instance *instance; | |||
struct sockaddr_in clientaddr; | |||
+ struct sockaddr_in destaddr; | |||
+ int sender_fd; // shared between several clients socket (bound to `destaddr`) | |||
struct event timeout; | |||
struct bufferevent *relay; | |||
struct event udprelay; | |||
diff --git a/utils.c b/utils.c | |||
index 6e1f3af..afdeea8 100644 | |||
--- a/utils.c | |||
+++ b/utils.c | |||
@@ -26,17 +26,54 @@ | |||
#include "utils.h" | |||
#include "redsocks.h" // for redsocks_close | |||
-int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr) | |||
+int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr) | |||
{ | |||
socklen_t addrlen = sizeof(*inaddr); | |||
ssize_t pktlen; | |||
- | |||
- pktlen = recvfrom(fd, buf, buflen, 0, (struct sockaddr*)inaddr, &addrlen); | |||
+ struct msghdr msg; | |||
+ struct iovec io; | |||
+ char control[1024]; | |||
+ | |||
+ memset(&msg, 0, sizeof(msg)); | |||
+ msg.msg_name = inaddr; | |||
+ msg.msg_namelen = sizeof(*inaddr); | |||
+ msg.msg_iov = &io; | |||
+ msg.msg_iovlen = 1; | |||
+ msg.msg_control = control; | |||
+ msg.msg_controllen = sizeof(control); | |||
+ io.iov_base = buf; | |||
+ io.iov_len = buflen; | |||
+ | |||
+ pktlen = recvmsg(fd, &msg, 0); | |||
if (pktlen == -1) { | |||
log_errno(LOG_WARNING, "recvfrom"); | |||
return -1; | |||
} | |||
+ if (toaddr) { | |||
+ memset(toaddr, 0, sizeof(*toaddr)); | |||
+ for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | |||
+ if ( | |||
+ cmsg->cmsg_level == SOL_IP && | |||
+ cmsg->cmsg_type == IP_ORIGDSTADDR && | |||
+ cmsg->cmsg_len >= CMSG_LEN(sizeof(*toaddr)) | |||
+ ) { | |||
+ struct sockaddr_in* cmsgaddr = (struct sockaddr_in*)CMSG_DATA(cmsg); | |||
+ char buf[RED_INET_ADDRSTRLEN]; | |||
+ log_error(LOG_DEBUG, "IP_ORIGDSTADDR: %s", red_inet_ntop(cmsgaddr, buf, sizeof(buf))); | |||
+ memcpy(toaddr, cmsgaddr, sizeof(*toaddr)); | |||
+ } | |||
+ else { | |||
+ log_error(LOG_WARNING, "unexepcted cmsg (level,type) = (%d,%d)", | |||
+ cmsg->cmsg_level, cmsg->cmsg_type); | |||
+ } | |||
+ } | |||
+ if (toaddr->sin_family != AF_INET) { | |||
+ log_error(LOG_WARNING, "(SOL_IP, IP_ORIGDSTADDR) not found"); | |||
+ return -1; | |||
+ } | |||
+ } | |||
+ | |||
if (addrlen != sizeof(*inaddr)) { | |||
log_error(LOG_WARNING, "unexpected address length %u instead of %zu", addrlen, sizeof(*inaddr)); | |||
return -1; | |||
diff --git a/utils.h b/utils.h | |||
index d3af00f..c2277e9 100644 | |||
--- a/utils.h | |||
+++ b/utils.h | |||
@@ -44,7 +44,7 @@ char *redsocks_evbuffer_readline(struct evbuffer *buf); | |||
struct bufferevent* red_connect_relay(struct sockaddr_in *addr, evbuffercb writecb, everrorcb errorcb, void *cbarg); | |||
int red_socket_geterrno(struct bufferevent *buffev); | |||
int red_is_socket_connected_ok(struct bufferevent *buffev); | |||
-int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr); | |||
+int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *fromaddr, struct sockaddr_in *toaddr); | |||
int fcntl_nonblock(int fd); | |||
-- | |||
1.9.1 | |||
@ -0,0 +1,25 @@ | |||
From b60b492602448b59aea194afd4991910d3613e5c Mon Sep 17 00:00:00 2001 | |||
From: Cody Schafer <jmesmon@gmail.com> | |||
Date: Tue, 24 Apr 2012 04:33:13 -0500 | |||
Subject: [PATCH 04/12] Fix transposition of memset parameters. | |||
--- | |||
utils.c | 2 +- | |||
1 file changed, 1 insertion(+), 1 deletion(-) | |||
diff --git a/utils.c b/utils.c | |||
index afdeea8..31c6894 100644 | |||
--- a/utils.c | |||
+++ b/utils.c | |||
@@ -222,7 +222,7 @@ char *red_inet_ntop(const struct sockaddr_in* sa, char* buffer, size_t buffer_si | |||
assert(buffer_size >= sizeof(placeholder)); | |||
- memset(buffer, buffer_size, 0); | |||
+ memset(buffer, 0, buffer_size); | |||
if (sa->sin_family == AF_INET) { | |||
retval = inet_ntop(AF_INET, &sa->sin_addr, buffer, buffer_size); | |||
port = ((struct sockaddr_in*)sa)->sin_port; | |||
-- | |||
1.9.1 | |||
@ -0,0 +1,105 @@ | |||
From 18e2b5ed1ffb3e7c5dfec8ff41b3027163f680ed Mon Sep 17 00:00:00 2001 | |||
From: Leonid Evdokimov <leon@darkk.net.ru> | |||
Date: Wed, 12 Sep 2012 02:05:39 +0400 | |||
Subject: [PATCH 09/12] Fix compilation on Ubuntu 10.04 LTS and (hopefully) | |||
Debian squeeze[1] | |||
fixes #28, fixes #22, fixes #24 | |||
[1] current "stable" release | |||
--- | |||
libc-compat.h | 25 +++++++++++++++++++++++++ | |||
libevent-compat.h | 11 +++++++++++ | |||
redsocks.c | 1 + | |||
redudp.c | 1 + | |||
utils.c | 1 + | |||
5 files changed, 39 insertions(+) | |||
create mode 100644 libc-compat.h | |||
create mode 100644 libevent-compat.h | |||
diff --git a/libc-compat.h b/libc-compat.h | |||
new file mode 100644 | |||
index 0000000..adcf63b | |||
--- /dev/null | |||
+++ b/libc-compat.h | |||
@@ -0,0 +1,25 @@ | |||
+#ifndef UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 | |||
+#define UUID_67C91670_FCCB_4855_BDF7_609F1EECB8B4 | |||
+ | |||
+/* all these definitions, are included into bits/in.h from libc6-dev 2.15-0ubuntu10 | |||
+ * from Ubuntu 12.04 and is not included into libc6-dev 2.11.1-0ubuntu7.10 from | |||
+ * Ubuntu 10.04. | |||
+ * linux/in.h is not included directly because of lots of redefinitions, | |||
+ * extracting single value from linux/in.h is not done because it looks like | |||
+ * autotools reinvention */ | |||
+#ifndef IP_ORIGDSTADDR | |||
+# warning Using hardcoded value for IP_ORIGDSTADDR as libc headers do not define it. | |||
+# define IP_ORIGDSTADDR 20 | |||
+#endif | |||
+ | |||
+#ifndef IP_RECVORIGDSTADDR | |||
+# warning Using hardcoded value for IP_RECVORIGDSTADDR as libc headers do not define it. | |||
+# define IP_RECVORIGDSTADDR IP_ORIGDSTADDR | |||
+#endif | |||
+ | |||
+#ifndef IP_TRANSPARENT | |||
+# warning Using hardcoded value for IP_TRANSPARENT as libc headers do not define it. | |||
+# define IP_TRANSPARENT 19 | |||
+#endif | |||
+ | |||
+#endif // 67C91670_FCCB_4855_BDF7_609F1EECB8B4 | |||
diff --git a/libevent-compat.h b/libevent-compat.h | |||
new file mode 100644 | |||
index 0000000..a7f1ca1 | |||
--- /dev/null | |||
+++ b/libevent-compat.h | |||
@@ -0,0 +1,11 @@ | |||
+#ifndef UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E | |||
+#define UUID_FC148CFA_5ECC_488E_8A62_CD39406C9F1E | |||
+ | |||
+/* evutil_socket_t is macros in libevent-2.0, not typedef, libevent-1.4 is | |||
+ * still supported because of Ubuntu 10.04 LTS */ | |||
+#ifndef evutil_socket_t | |||
+# warning Using hardcoded value for evutil_socket_t as libevent headers do not define it. | |||
+# define evutil_socket_t int | |||
+#endif | |||
+ | |||
+#endif // FC148CFA_5ECC_488E_8A62_CD39406C9F1E | |||
diff --git a/redsocks.c b/redsocks.c | |||
index ba5eab2..878576f 100644 | |||
--- a/redsocks.c | |||
+++ b/redsocks.c | |||
@@ -33,6 +33,7 @@ | |||
#include "base.h" | |||
#include "redsocks.h" | |||
#include "utils.h" | |||
+#include "libevent-compat.h" | |||
#define REDSOCKS_RELAY_HALFBUFF 4096 | |||
diff --git a/redudp.c b/redudp.c | |||
index 262af3e..05460dc 100644 | |||
--- a/redudp.c | |||
+++ b/redudp.c | |||
@@ -32,6 +32,7 @@ | |||
#include "main.h" | |||
#include "redsocks.h" | |||
#include "redudp.h" | |||
+#include "libc-compat.h" | |||
#define redudp_log_error(client, prio, msg...) \ | |||
redsocks_log_write_plain(__FILE__, __LINE__, __func__, 0, &(client)->clientaddr, get_destaddr(client), prio, ## msg) | |||
diff --git a/utils.c b/utils.c | |||
index 31c6894..7de3969 100644 | |||
--- a/utils.c | |||
+++ b/utils.c | |||
@@ -25,6 +25,7 @@ | |||
#include "log.h" | |||
#include "utils.h" | |||
#include "redsocks.h" // for redsocks_close | |||
+#include "libc-compat.h" | |||
int red_recv_udp_pkt(int fd, char *buf, size_t buflen, struct sockaddr_in *inaddr, struct sockaddr_in *toaddr) | |||
{ | |||
-- | |||
1.9.1 | |||
@ -0,0 +1,17 @@ | |||
Description: change the default config file location | |||
redsocks by default looks for ./redsocks.conf. Change this to | |||
/etc/redsocks.conf for a more deterministic behaviour. | |||
Author: Apollon Oikonomopoulos <apoikos@gmail.com> | |||
Forwared: no | |||
Last-Update: 2013-04-23 | |||
--- a/main.c | |||
+++ b/main.c | |||
@@ -39,7 +39,7 @@ | |||
&dnstc_subsys, | |||
}; | |||
-static const char *confname = "redsocks.conf"; | |||
+static const char *confname = "/etc/redsocks.conf"; | |||
static const char *pidfile = NULL; | |||
static void terminate(int sig, short what, void *_arg) |