Request: upstream via a SOCKS proxy
Tom van der Woerdt
info at tvdw.eu
Fri Jan 25 21:46:48 UTC 2013
So I invested some time in this today, and came up with this patch that
implements SOCKS5 support. Can anyone suggest improvements?
Sample config :
upstream backend {
server 127.0.0.1:9050 socks=ip4.me;
}
server {
listen 1234;
server_name localhost;
location / {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_set_header Host ip4.me;
}
}
No DNS lookups are done at nginx, which is proper behavior.
Index: src/http/ngx_http_upstream_round_robin.c
===================================================================
--- src/http/ngx_http_upstream_round_robin.c (revision 5017)
+++ src/http/ngx_http_upstream_round_robin.c (working copy)
@@ -87,6 +87,12 @@
peers->peer[n].weight = server[i].weight;
peers->peer[n].effective_weight = server[i].weight;
peers->peer[n].current_weight = 0;
+
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ peers->peer[n].socks = server[i].socks;
+ peers->peer[n].socks_port = server[i].socks_port;
+ peers->peer[n].socks_hostname = server[i].socks_hostname;
+#endif
n++;
}
}
@@ -145,6 +151,12 @@
backup->peer[n].max_fails = server[i].max_fails;
backup->peer[n].fail_timeout = server[i].fail_timeout;
backup->peer[n].down = server[i].down;
+
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ backup->peer[n].socks = server[i].socks;
+ backup->peer[n].socks_port = server[i].socks_port;
+ backup->peer[n].socks_hostname = server[i].socks_hostname;
+#endif
n++;
}
}
@@ -453,6 +465,12 @@
pc->socklen = peer->socklen;
pc->name = &peer->name;
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ pc->socks = peer->socks;
+ pc->socks_port = peer->socks_port;
+ pc->socks_hostname = peer->socks_hostname;
+#endif
+
/* ngx_unlock_mutex(rrp->peers->mutex); */
if (pc->tries == 1 && rrp->peers->next) {
Index: src/http/ngx_http_upstream.c
===================================================================
--- src/http/ngx_http_upstream.c (revision 5017)
+++ src/http/ngx_http_upstream.c (working copy)
@@ -146,7 +146,12 @@
static void ngx_http_upstream_ssl_handshake(ngx_connection_t *c);
#endif
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+void ngx_upstream_socks_init_handshake(ngx_http_request_t *,
+ ngx_http_upstream_t *);
+#endif
+
ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = {
{ ngx_string("Status"),
@@ -1228,6 +1233,15 @@
u->request_sent = 0;
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+
+ if (u->peer.socks) {
+ ngx_upstream_socks_init_handshake(r, u);
+ return;
+ }
+
+#endif
+
if (rc == NGX_AGAIN) {
ngx_add_timer(c->write, u->conf->connect_timeout);
return;
@@ -4131,7 +4145,8 @@
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
- |NGX_HTTP_UPSTREAM_BACKUP);
+ |NGX_HTTP_UPSTREAM_BACKUP
+ |NGX_HTTP_UPSTREAM_SOCKS_FLAG);
if (uscf == NULL) {
return NGX_CONF_ERROR;
}
@@ -4334,6 +4349,29 @@
continue;
}
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ if (ngx_strncmp(value[i].data, "socks=", 6) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_SOCKS_FLAG)) {
+ goto invalid;
+ }
+
+ if (us->socks) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate socks proxy");
+
+ goto invalid;
+ }
+
+ us->socks = 1;
+ us->socks_port = 80;
+ us->socks_hostname.len = value[i].len - 6;
+ us->socks_hostname.data = value[i].data + 6;
+
+ continue;
+ }
+#endif
+
goto invalid;
}
Index: src/http/ngx_http_upstream.h
===================================================================
--- src/http/ngx_http_upstream.h (revision 5017)
+++ src/http/ngx_http_upstream.h (working copy)
@@ -93,6 +93,12 @@
unsigned down:1;
unsigned backup:1;
+
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ unsigned socks:1;
+ in_port_t socks_port;
+ ngx_str_t socks_hostname;
+#endif
} ngx_http_upstream_server_t;
@@ -102,6 +108,7 @@
#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008
#define NGX_HTTP_UPSTREAM_DOWN 0x0010
#define NGX_HTTP_UPSTREAM_BACKUP 0x0020
+#define NGX_HTTP_UPSTREAM_SOCKS_FLAG 0x0040
struct ngx_http_upstream_srv_conf_s {
@@ -332,6 +339,11 @@
unsigned request_sent:1;
unsigned header_sent:1;
+
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ ngx_http_upstream_handler_pt next_read_event_handler;
+ ngx_http_upstream_handler_pt next_write_event_handler;
+#endif
};
Index: src/http/ngx_http_upstream_round_robin.h
===================================================================
--- src/http/ngx_http_upstream_round_robin.h (revision 5017)
+++ src/http/ngx_http_upstream_round_robin.h (working copy)
@@ -35,6 +35,12 @@
#if (NGX_HTTP_SSL)
ngx_ssl_session_t *ssl_session; /* local to a
process */
#endif
+
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ unsigned socks:1;
+ in_port_t socks_port;
+ ngx_str_t socks_hostname;
+#endif
} ngx_http_upstream_rr_peer_t;
Index: src/http/modules/ngx_http_upstream_socks_module.c
===================================================================
--- src/http/modules/ngx_http_upstream_socks_module.c (revision 0)
+++ src/http/modules/ngx_http_upstream_socks_module.c (revision 0)
@@ -0,0 +1,133 @@
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+static void ngx_upstream_socks_write_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_upstream_socks_read_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+
+void
+ngx_upstream_socks_init_handshake(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ u->next_write_event_handler = u->write_event_handler;
+ u->next_read_event_handler = u->read_event_handler;
+
+ u->write_event_handler = ngx_upstream_socks_write_handler;
+ u->read_event_handler = ngx_upstream_socks_read_handler;
+}
+
+static void
+ngx_upstream_socks_write_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http socks upstream handshake handler");
+
+ if (c->write->timedout) {
+ ngx_http_finalize_request(r, NGX_HTTP_GATEWAY_TIME_OUT);
+ return;
+ }
+
+ if (!u->peer.socks_handshake1_sent) {
+ u->peer.socks_handshake1_sent = 1;
+
+ // TODO, this is ugly
+ u_char handshake[] = {0x05, 0x01, 0x00};
+ c->send(c, handshake, 3);
+
+ } else if (u->peer.socks_handshake2_recv &&
!u->peer.socks_handshake3_sent) {
+ u_char *handshake;
+ ngx_uint_t len;
+
+ len = 7 + u->peer.socks_hostname.len;
+
+ handshake = ngx_pnalloc(r->pool, len);
+
+ handshake[0] = 5; // version
+ handshake[1] = 1; // connect
+ handshake[2] = 0;
+ handshake[3] = 3; // specify dns
+ handshake[4] = (u_char)u->peer.socks_hostname.len;
+
+ // port
+ *(u_short*)(handshake+len-2) = ntohs(u->peer.socks_port);
+
+ ngx_memcpy(handshake+5, u->peer.socks_hostname.data,
u->peer.socks_hostname.len);
+
+ c->send(c, handshake, len);
+
+ ngx_pfree(r->pool, handshake);
+
+ u->peer.socks_handshake3_sent = 1;
+ }
+}
+
+static void
+ngx_upstream_socks_read_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http socks upstream handshake recv handler");
+
+ if (c->read->timedout) {
+ ngx_http_finalize_request(r, NGX_HTTP_GATEWAY_TIME_OUT);
+ return;
+ }
+
+ if (!u->peer.socks_handshake2_recv) {
+ u_char buf[2];
+
+ u->peer.socks_handshake2_recv = 1;
+ c->recv(c, buf, 2);
+
+ if (buf[0] != 5 || buf[1] != 0) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_GATEWAY);
+ return;
+ }
+
+ } else if (u->peer.socks_handshake3_sent &&
!u->peer.socks_handshake4_recv) {
+ u_char buf[22];
+
+ c->recv(c, buf, 5);
+
+ if (buf[0] != 5 || buf[1] != 0 || buf[2] != 0) {
+ ngx_http_finalize_request(r, NGX_HTTP_BAD_GATEWAY);
+ return;
+ }
+
+ if (buf[3] == 1) {
+ c->recv(c, buf+5, 5);
+
+ } else if (buf[3] == 4) {
+ c->recv(c, buf+5, 17);
+
+ } else if (buf[3] == 3) {
+ u_char *hostname_and_port = ngx_pnalloc(r->pool,
((size_t)buf[4]) + 2);
+ c->recv(c, hostname_and_port, ((size_t)buf[4]) + 2);
+ ngx_pfree(r->pool, hostname_and_port);
+ }
+
+ u->peer.socks_handshake4_recv = 1;
+
+ // restore previous handlers
+ u->write_event_handler = u->next_write_event_handler;
+ u->read_event_handler = u->next_read_event_handler;
+
+ u->write_event_handler(r, u);
+ }
+}
Index: src/event/ngx_event_connect.h
===================================================================
--- src/event/ngx_event_connect.h (revision 5017)
+++ src/event/ngx_event_connect.h (working copy)
@@ -52,6 +52,16 @@
ngx_event_save_peer_session_pt save_session;
#endif
+#if (NGX_HTTP_UPSTREAM_SOCKS)
+ unsigned socks:1;
+ unsigned socks_handshake1_sent:1;
+ unsigned socks_handshake2_recv:1;
+ unsigned socks_handshake3_sent:1;
+ unsigned socks_handshake4_recv:1;
+ in_port_t socks_port;
+ ngx_str_t socks_hostname;
+#endif
+
#if (NGX_THREADS)
ngx_atomic_t *lock;
#endif
Index: auto/options
===================================================================
--- auto/options (revision 5017)
+++ auto/options (working copy)
@@ -99,6 +99,7 @@
HTTP_UPSTREAM_IP_HASH=YES
HTTP_UPSTREAM_LEAST_CONN=YES
HTTP_UPSTREAM_KEEPALIVE=YES
+HTTP_UPSTREAM_SOCKS=NO
# STUB
HTTP_STUB_STATUS=NO
@@ -216,6 +217,8 @@
--with-http_random_index_module) HTTP_RANDOM_INDEX=YES ;;
--with-http_secure_link_module) HTTP_SECURE_LINK=YES ;;
--with-http_degradation_module) HTTP_DEGRADATION=YES ;;
+ --with-http_upstream_socks_module)
+ HTTP_UPSTREAM_SOCKS=YES ;;
--without-http_charset_module) HTTP_CHARSET=NO ;;
--without-http_gzip_module) HTTP_GZIP=NO ;;
@@ -364,6 +367,7 @@
--with-http_secure_link_module enable ngx_http_secure_link_module
--with-http_degradation_module enable ngx_http_degradation_module
--with-http_stub_status_module enable ngx_http_stub_status_module
+ --with-http_upstream_socks_module enable SOCKS5 support for upstreams
--without-http_charset_module disable ngx_http_charset_module
--without-http_gzip_module disable ngx_http_gzip_module
Index: auto/modules
===================================================================
--- auto/modules (revision 5017)
+++ auto/modules (working copy)
@@ -365,6 +365,11 @@
HTTP_SRCS="$HTTP_SRCS $HTTP_UPSTREAM_KEEPALIVE_SRCS"
fi
+if [ $HTTP_UPSTREAM_SOCKS = YES ]; then
+ have=NGX_HTTP_UPSTREAM_SOCKS . auto/have
+ HTTP_SRCS="$HTTP_SRCS
src/http/modules/ngx_http_upstream_socks_module.c"
+fi
+
if [ $HTTP_STUB_STATUS = YES ]; then
have=NGX_STAT_STUB . auto/have
HTTP_MODULES="$HTTP_MODULES ngx_http_stub_status_module"
Tom
Op 1/25/13 1:13 PM, Tom van der Woerdt schreef:
> Yes, I currently use a proxy like that, but it feels like a
> performance killer to do it like that. If implemented in nginx it
> could be so much faster.
>
> About SOCKS implementations: as long as authentication isn't required,
> the handshake is really, really easy, especially version 4. The lack
> of a framing protocol makes it behave like any normal socket once the
> handshake is done.
>
> Tom
>
>
> Op 1/25/13 12:57 PM, Aleksandar Lazic schreef:
>> Hi,
>>
>> There are some http2socks proxy out there.
>>
>> http://www.privoxy.org/
>> http://www.privoxy.org/user-manual/config.html#SOCKS
>>
>> http://www.delegate.org/delegate/
>> http://www.delegate.org/delegate/Manual.htm#SOCKS
>>
>> http://en.wikipedia.org/wiki/SOCKS#Translating_proxies
>>
>> The setup coul looks like
>>
>> client -> nginx -> http-proxylistener -> socks-proxyrequester ->
>> socks-server
>>
>> OT: Sock5 is not so easy if you want to implement the full protocol,
>> imho.
>>
>> I Agree with you that this would be a nice upsteam module, even that
>> I don't
>> need it at the moment.
>>
>> Cheers
>> Aleks
>> Am 23-01-2013 17:05, schrieb Tom van der Woerdt:
>>> Hi,
>>>
>>> A project I'm working on has a backend server that, for security
>>> reasons, can only be accessed via a SOCKS4a/SOCKS5 proxy. A frontend
>>> server for this project (nginx) has one simple task: to proxy all
>>> incoming connections to the backend server.
>>>
>>> Right now, nginx cannot do this, because it has no support for
>>> proxying upstream connections via a SOCKS proxy. The current temporary
>>> workaround is to run another service on the frontend machine that acts
>>> like a HTTP server but proxies the data to the backend - basically
>>> everything I'd like nginx to do. I cannot use this service as my main
>>> frontend, because there are a few other files that also need to be
>>> served.
>>>
>>> SOCKS4a and SOCKS5 are really easy protocols and are basically just
>>> sockets but with an alternate handshake (skip the DNS lookup, send the
>>> hostname to the socket instead). Since they should be so easy to
>>> implement, I'm requesting that on this mailing list.
>>>
>>> I was thinking of a config file that would look something like this :
>>>
>>> upstream backend {
>>> server hidden_dns.local socks4=127.0.0.1:1234;
>>> }
>>>
>>> server {
>>> location / {
>>> proxy_pass http://backend;
>>> }
>>> }
>>>
>>> As far as I'm aware, this feature wouldn't break anything, since a
>>> SOCKS connections behaves just like any other normal socket.
>>>
>>> Thanks for considering,
>>> Tom van der Woerdt
>>>
>>>
>>> _______________________________________________
>>> nginx-devel mailing list
>>> nginx-devel at nginx.org
>>> http://mailman.nginx.org/mailman/listinfo/nginx-devel
>>
>> _______________________________________________
>> nginx-devel mailing list
>> nginx-devel at nginx.org
>> http://mailman.nginx.org/mailman/listinfo/nginx-devel
>
>
>
>
> _______________________________________________
> nginx-devel mailing list
> nginx-devel at nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx-devel/attachments/20130125/75e0142f/attachment-0001.html>
More information about the nginx-devel
mailing list