Also as: https://github.com/OpenVPN/openvpn/pull/6

I was exploring the possibility of using a SOCKS5 server to multiplex
OpenVPN connections based on hostname (in the remote option), akin to
how HTTP reverse proxies commonly delegate to a physical server
according to the HTTP 1.1 Host header—so that many hostnames can map
to the same IP of the reverse proxy.

I found that the SOCKS5 protocol does support DOMAINNAME rather than
IP V4 when using the UDP relay server. However, OpenVPN was never
using this option; it always sent a preresolved IP (v4) address,
giving the proxy server no way of inferring what hostname was
originally requested.

This patch amends the socks_process_outgoing_udp method to send
DOMAINNAME for the first datagram of the connection, when an FQDN was
specified. Thereafter it sends IP V4since this is more compact—and
experimentation with the Dante server revealed that it
calledcgethostbyname on many, if not all, such datagrams it received,
which could be quite expensive. Since in the case of OpenVPN the
“desired destination address” is constant for the life of the tunnel,
it should suffice to send the hostname just once.

socks_adjust_frame_parameters also needed to be changed to allow for
the worst-case buffer size, a 255-character hostname (Linux generally
only allows 64) plus fixed-length fields (7 bytes). It would be nicer
to set this according to the actual hostname length, but I could not
see how to obtain that hostname during this early stage of
initialization.

Filing this for the purpose of getting feedback. Probably there needs
to be a user option to configure this behavior:

· disable (restoring original behavior; perhaps the default)
· enable for first packet only (as in the current patch)
· enable for all packets, which could be useful in case the reverse
proxy is using a single UDP relay server for many clients (since
SOCKS5 offers no apparent connection identifier other than the
BND.PORT)

Note that the original code probably does not work with IPv6, but I
did not attempt to address that in this patch.

 src/openvpn/forward.c |  2 +-
 src/openvpn/openvpn.h |  5 +++++
 src/openvpn/socks.c   | 38 +++++++++++++++++++++++++++-----------
 src/openvpn/socks.h   |  3 +--
 4 files changed, 34 insertions(+), 14 deletions(-)

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 447b86d..ba084aa 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -631,7 +631,7 @@ socks_preprocess_outgoing_link (struct context *c,
 {
   if (c->c2.link_socket->socks_proxy && c->c2.link_socket->info.proto
== PROTO_UDPv4)
     {
-      *size_delta += socks_process_outgoing_udp (&c->c2.to_link,
c->c2.to_link_addr);
+      *size_delta += socks_process_outgoing_udp (c);
       *to_addr = &c->c2.link_socket->socks_relay;
     }
 }
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index 606a4f5..e040fe7 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -291,6 +291,11 @@ struct context_2
   counter_type n_trunc_post_decrypt;
 #endif

+#ifdef ENABLE_SOCKS
+  /* Set to true after first UDP packet sent over SOCKS, so we only
send ATYP=DOMAINNAME once */
+  bool socks_sent_hostname;
+#endif
+
   /*
    * Timer objects for ping and inactivity
    * timeout features.
diff --git a/src/openvpn/socks.c b/src/openvpn/socks.c
index 235982e..d98887f 100644
--- a/src/openvpn/socks.c
+++ b/src/openvpn/socks.c
@@ -49,14 +49,17 @@
 #include "proxy.h"

 #include "memdbg.h"
+#include "openvpn.h"

 #define UP_TYPE_SOCKS "SOCKS Proxy"

+/* See \c socks_process_outgoing_udp for motivation. */
 void
 socks_adjust_frame_parameters (struct frame *frame, int proto)
 {
   if (proto == PROTO_UDPv4)
-    frame_add_to_extra_link (frame, 10);
+    // TODO no access to remote_host at this time, so have to assume the worst
+    frame_add_to_extra_link (frame, 255 + 7);
 }

 struct socks_proxy_info *
@@ -510,33 +513,46 @@ socks_process_incoming_udp (struct buffer *buf,
 }

 /*
- * Add a 10 byte socks header prior to UDP write.
- * *to is the destination address.
+ * Add a socks header prior to UDP write.
  *
  * Run before UDP write.
  * Returns the size of the header.
  */
 int
-socks_process_outgoing_udp (struct buffer *buf,
-    const struct link_socket_actual *to)
+socks_process_outgoing_udp (struct context *c)
 {
+  struct link_socket_actual *to = c->c2.to_link_addr;
+  const char *host = c->c2.link_socket->remote_host;
   /*
-   * Get a 10 byte subset buffer prepended to buf --
+   * Get a subset buffer prepended to buf --
    * we expect these bytes will be here because
    * we allocated frame space in socks_adjust_frame_parameters.
    */
-  struct buffer head = buf_sub (buf, 10, true);
+  int len = c->c2.socks_sent_hostname ||
ip_addr_dotted_quad_safe(host) ? /* use ATYP = IP V4 */ 0 :
strlen(host);
+  c->c2.socks_sent_hostname = true; // only ever use ATYP =
DOMAINNAME once per connection
+  int size = len ? len + 7 : 10;
+  struct buffer head = buf_sub (&c->c2.to_link, size, true);

   /* crash if not enough headroom in buf */
   ASSERT (buf_defined (&head));

   buf_write_u16 (&head, 0); /* RSV = 0 */
   buf_write_u8 (&head, 0); /* FRAG = 0 */
-  buf_write_u8 (&head, '\x01'); /* ATYP = 1 (IP V4) */
-  buf_write (&head, &to->dest.addr.in4.sin_addr, sizeof
(to->dest.addr.in4.sin_addr));
-  buf_write (&head, &to->dest.addr.in4.sin_port, sizeof
(to->dest.addr.in4.sin_port));
+  if (len)
+    {
+      msg(M_INFO, "Sending hostname %s to SOCKS5 proxy", host);
+      buf_write_u8(&head, '\x03'); /* ATYP = 3 (DOMAINNAME) */
+      buf_write_u8(&head, len);
+      buf_write(&head, host, len);
+    }
+  else
+    {
+      buf_write_u8(&head, '\x01'); /* ATYP = 1 (IP V4) */
+      buf_write(&head, &to->dest.addr.in4.sin_addr, /* 4 */sizeof
(to->dest.addr.in4.sin_addr));
+    }
+  buf_write (&head, &to->dest.addr.in4.sin_port, /* 2 */sizeof
(to->dest.addr.in4.sin_port));

-  return 10;
+  return size;
 }

 #else
diff --git a/src/openvpn/socks.h b/src/openvpn/socks.h
index b55ff6f..a6160d2 100644
--- a/src/openvpn/socks.h
+++ b/src/openvpn/socks.h
@@ -70,8 +70,7 @@ void establish_socks_proxy_udpassoc (struct
socks_proxy_info *p,
 void socks_process_incoming_udp (struct buffer *buf,
  struct link_socket_actual *from);

-int socks_process_outgoing_udp (struct buffer *buf,
- const struct link_socket_actual *to);
+int socks_process_outgoing_udp (struct context *c);

 #endif
 #endif
-- 
1.8.1.2

Reply via email to