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