[Openvpn-devel,2/4] socket: introduce INDIRECT transport protocol abstraction

Message ID 20181230112901.29241-3-a@unstable.cc
State Changes Requested
Headers show
Series Transport API: offload traffic manipulation to plugins | expand

Commit Message

Antonio Quartulli Dec. 30, 2018, 12:28 a.m. UTC
From: Robin Tarsiger <rtt@dasyatidae.com>

This new transport protocol is used to tell the core code that traffic
should not be directly processed, but should rather be rerouted to a
transport plugin. It is basically an abstraction as it does not say tell
the code how to process the data, but simply forces its redirection to
the external code.

Signed-off-by: Robin Tarsiger <rtt@dasyatidae.com>
[antonio@openvpn.net: refactored commits, restyled code]
---
 src/openvpn/forward.c   |   5 ++
 src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
 src/openvpn/socket.h    |  70 +++++++++++++++++++
 src/openvpn/transport.h |   5 ++
 4 files changed, 222 insertions(+), 4 deletions(-)

Comments

Arne Schwabe Jan. 22, 2019, 6:22 a.m. UTC | #1
Am 30.12.18 um 12:28 schrieb Antonio Quartulli:
> From: Robin Tarsiger <rtt@dasyatidae.com>
> 
> This new transport protocol is used to tell the core code that traffic
> should not be directly processed, but should rather be rerouted to a
> transport plugin. It is basically an abstraction as it does not say tell
> the code how to process the data, but simply forces its redirection to
> the external code.
> 
> Signed-off-by: Robin Tarsiger <rtt@dasyatidae.com>
> [antonio@openvpn.net: refactored commits, restyled code]
> ---
>  src/openvpn/forward.c   |   5 ++
>  src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
>  src/openvpn/socket.h    |  70 +++++++++++++++++++
>  src/openvpn/transport.h |   5 ++
>  4 files changed, 222 insertions(+), 4 deletions(-)
> 
> diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
> index 0a90fff0..a7092c7e 100644
> --- a/src/openvpn/forward.c
> +++ b/src/openvpn/forward.c
> @@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
>              {
>                  int i;
>                  c->c2.event_set_status = 0;
> +#ifdef ENABLE_PLUGIN
> +                c->c2.event_set_status |=
> +                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)

The & 3 looks like some defined should be used instead.



>      if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)
>      {
>          sock->sd = create_socket_udp(addr, sock->sockflags);
> @@ -2279,7 +2320,11 @@ link_socket_init_phase2(struct link_socket *sock,
>          }
>  
>          /* If socket has not already been created create it now */
> -        if (sock->sd == SOCKET_UNDEFINED)
> +        if (sock->sd == SOCKET_UNDEFINED
> +#ifdef ENABLE_PLUGIN
> +            && !sock->indirect
> +#endif
> +            )
>          {
>              /* If we have no --remote and have still not figured out the
>               * protocol family to use we will use the first of the bind */
> @@ -2300,7 +2345,11 @@ link_socket_init_phase2(struct link_socket *sock,
>          }
>  
>          /* Socket still undefined, give a warning and abort connection */
> -        if (sock->sd == SOCKET_UNDEFINED)
> +        if (sock->sd == SOCKET_UNDEFINED
> +#ifdef ENABLE_PLUGIN
> +            && !sock->indirect
> +#endif
> +            )

Better use the inline funtcion proto_is_indirect (or similar
sock_is_indirect) that always returns false here and ifdef in header
than to add the ifdefs inline in the code.


>  bool
> @@ -3167,6 +3236,10 @@ proto_is_net(int proto)
>  bool
>  proto_is_dgram(int proto)
>  {
> +    if (proto_is_indirect(proto))
> +    {
> +        return true;
> +    }
>      return proto_is_udp(proto);
>  }

I think here need to be a good explaination why indirect is dgram but
not udp and also proto_is_dgram and proto_is_udp need to get some
comment to explain their difference in usage as this is now different.

>  
> @@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
>          return "TCPv4_CLIENT";
>      }
>  
> +#ifdef ENABLE_PLUGIN
> +    if (proto == PROTO_INDIRECT)
> +    {
> +        /* FIXME: the string reported here should match the actual transport
> +         * protocol being used, however in this function we have no knowledge of
> +         * what protocol is exactly being used by the transport-plugin.
> +         * Therefore we simply return INDIRECT for now.
> +         */
> +        return "INDIRECT";
> +    }
> +#endif

From reading the code this function is only used in creating the string
in options_string which needs to match between peers. As the plugin
emulates UDP/DGRAM behaviour I think we should instead return here the
same as a real UDP protocol.

>  
> +#ifdef ENABLE_PLUGIN
> +
> +int link_socket_read_indirect(struct link_socket *sock,
> +                              struct buffer *buf,
> +                              struct link_socket_actual *from);
> +
> +int link_socket_write_indirect(struct link_socket *sock,
> +                               struct buffer *buf,
> +                               struct link_socket_actual *from);
> +
> +bool proto_is_indirect(int proto);
> +

This prototype definition looks a bit weird here. I see not reason why
the real proto_is_indirect cannot be here

> +#else  /* ifdef ENABLE_PLUGIN */
> +
> +static int
> +link_socket_read_indirect(struct link_socket *sock,
> +
> +static int
> +link_socket_write_indirect(struct link_socket *sock,

> +static bool
> +proto_is_indirect(int proto)

I think functions implemented in the header should have the inline
keyword. At least the rest of the header files do it that way.


Arne
Antonio Quartulli Jan. 28, 2019, 5:47 p.m. UTC | #2
Hi,

On 23/01/2019 03:22, Arne Schwabe wrote:
> Am 30.12.18 um 12:28 schrieb Antonio Quartulli:
>> From: Robin Tarsiger <rtt@dasyatidae.com>
>>
>> This new transport protocol is used to tell the core code that traffic
>> should not be directly processed, but should rather be rerouted to a
>> transport plugin. It is basically an abstraction as it does not say tell
>> the code how to process the data, but simply forces its redirection to
>> the external code.
>>
>> Signed-off-by: Robin Tarsiger <rtt@dasyatidae.com>
>> [antonio@openvpn.net: refactored commits, restyled code]
>> ---
>>  src/openvpn/forward.c   |   5 ++
>>  src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
>>  src/openvpn/socket.h    |  70 +++++++++++++++++++
>>  src/openvpn/transport.h |   5 ++
>>  4 files changed, 222 insertions(+), 4 deletions(-)
>>
>> diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
>> index 0a90fff0..a7092c7e 100644
>> --- a/src/openvpn/forward.c
>> +++ b/src/openvpn/forward.c
>> @@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
>>              {
>>                  int i;
>>                  c->c2.event_set_status = 0;
>> +#ifdef ENABLE_PLUGIN
>> +                c->c2.event_set_status |=
>> +                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)
> 
> The & 3 looks like some defined should be used instead.

I think this is simply about fetching the last two bits..not much fuzz
here..this is mostly because of how event_set_status works. But will see
if I find a meaningful name.

> 
> 
> 
>>      if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)
>>      {
>>          sock->sd = create_socket_udp(addr, sock->sockflags);
>> @@ -2279,7 +2320,11 @@ link_socket_init_phase2(struct link_socket *sock,
>>          }
>>  
>>          /* If socket has not already been created create it now */
>> -        if (sock->sd == SOCKET_UNDEFINED)
>> +        if (sock->sd == SOCKET_UNDEFINED
>> +#ifdef ENABLE_PLUGIN
>> +            && !sock->indirect
>> +#endif
>> +            )
>>          {
>>              /* If we have no --remote and have still not figured out the
>>               * protocol family to use we will use the first of the bind */
>> @@ -2300,7 +2345,11 @@ link_socket_init_phase2(struct link_socket *sock,
>>          }
>>  
>>          /* Socket still undefined, give a warning and abort connection */
>> -        if (sock->sd == SOCKET_UNDEFINED)
>> +        if (sock->sd == SOCKET_UNDEFINED
>> +#ifdef ENABLE_PLUGIN
>> +            && !sock->indirect
>> +#endif
>> +            )
> 
> Better use the inline funtcion proto_is_indirect (or similar
> sock_is_indirect) that always returns false here and ifdef in header
> than to add the ifdefs inline in the code.

agreed! I am all for that! will change

> 
> 
>>  bool
>> @@ -3167,6 +3236,10 @@ proto_is_net(int proto)
>>  bool
>>  proto_is_dgram(int proto)
>>  {
>> +    if (proto_is_indirect(proto))
>> +    {
>> +        return true;
>> +    }
>>      return proto_is_udp(proto);
>>  }
> 
> I think here need to be a good explaination why indirect is dgram but
> not udp and also proto_is_dgram and proto_is_udp need to get some
> comment to explain their difference in usage as this is now different.

I wouldn't say is_udp and is_dgram are now different, but is_dgram has
been extended to account for the indirect case as well.

> 
>>  
>> @@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
>>          return "TCPv4_CLIENT";
>>      }
>>  
>> +#ifdef ENABLE_PLUGIN
>> +    if (proto == PROTO_INDIRECT)
>> +    {
>> +        /* FIXME: the string reported here should match the actual transport
>> +         * protocol being used, however in this function we have no knowledge of
>> +         * what protocol is exactly being used by the transport-plugin.
>> +         * Therefore we simply return INDIRECT for now.
>> +         */
>> +        return "INDIRECT";
>> +    }
>> +#endif
> 
> From reading the code this function is only used in creating the string
> in options_string which needs to match between peers. As the plugin
> emulates UDP/DGRAM behaviour I think we should instead return here the
> same as a real UDP protocol.

well, the problem here is that if I use transport-plugin1 and you use
transport-plugin2 these strings will match between client and server
even though they should not (because the protocols being used are
actually different).

I don't think INDIRECT is UDP only. That is just how it prefers to be
treated, but could be non-UDP as well, I think. (need to double check)

> 
>>  
>> +#ifdef ENABLE_PLUGIN
>> +
>> +int link_socket_read_indirect(struct link_socket *sock,
>> +                              struct buffer *buf,
>> +                              struct link_socket_actual *from);
>> +
>> +int link_socket_write_indirect(struct link_socket *sock,
>> +                               struct buffer *buf,
>> +                               struct link_socket_actual *from);
>> +
>> +bool proto_is_indirect(int proto);
>> +
> 
> This prototype definition looks a bit weird here. I see not reason why
> the real proto_is_indirect cannot be here

you mean the proto_is_indirect() implementation should be here in the
header? Why?

> 
>> +#else  /* ifdef ENABLE_PLUGIN */
>> +
>> +static int
>> +link_socket_read_indirect(struct link_socket *sock,
>> +
>> +static int
>> +link_socket_write_indirect(struct link_socket *sock,
> 
>> +static bool
>> +proto_is_indirect(int proto)
> 
> I think functions implemented in the header should have the inline
> keyword. At least the rest of the header files do it that way.

yeah they should. will fix that.

> 
> 
> Arne
>
Arne Schwabe Jan. 29, 2019, 5:45 a.m. UTC | #3
>>> +++ b/src/openvpn/forward.c
>>> @@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
>>>              {
>>>                  int i;
>>>                  c->c2.event_set_status = 0;
>>> +#ifdef ENABLE_PLUGIN
>>> +                c->c2.event_set_status |=
>>> +                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)
>>
>> The & 3 looks like some defined should be used instead.
> 
> I think this is simply about fetching the last two bits..not much fuzz
> here..this is mostly because of how event_set_status works. But will see
> if I find a meaningful name.
> 

INDIRECT_FLAG_MASK or something.

>>>  bool
>>> @@ -3167,6 +3236,10 @@ proto_is_net(int proto)
>>>  bool
>>>  proto_is_dgram(int proto)
>>>  {
>>> +    if (proto_is_indirect(proto))
>>> +    {
>>> +        return true;
>>> +    }
>>>      return proto_is_udp(proto);
>>>  }
>>
>> I think here need to be a good explaination why indirect is dgram but
>> not udp and also proto_is_dgram and proto_is_udp need to get some
>> comment to explain their difference in usage as this is now different.
> 
> I wouldn't say is_udp and is_dgram are now different, but is_dgram has
> been extended to account for the indirect case as well.
> 

is_dgram and is_udp return different values for INDIRECT, so they are
different. I would like some comment why that is. Without having looked
deeper I suspect the one function is used to determine the wire format
and the other is used for determing the actual outer protocol. Just
adding that as comment in the is_dgram function would be nice (double
check first).

>>
>>>  
>>> @@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
>>>          return "TCPv4_CLIENT";
>>>      }
>>>  
>>> +#ifdef ENABLE_PLUGIN
>>> +    if (proto == PROTO_INDIRECT)
>>> +    {
>>> +        /* FIXME: the string reported here should match the actual transport
>>> +         * protocol being used, however in this function we have no knowledge of
>>> +         * what protocol is exactly being used by the transport-plugin.
>>> +         * Therefore we simply return INDIRECT for now.
>>> +         */
>>> +        return "INDIRECT";
>>> +    }
>>> +#endif
>>
>> From reading the code this function is only used in creating the string
>> in options_string which needs to match between peers. As the plugin
>> emulates UDP/DGRAM behaviour I think we should instead return here the
>> same as a real UDP protocol.
> 
> well, the problem here is that if I use transport-plugin1 and you use
> transport-plugin2 these strings will match between client and server
> even though they should not (because the protocols being used are
> actually different).

If we get to the point that the cleartext inside the openvpn tunnel is
compared the methods are compatible. And if you have a client on the one
side that has plugin in it and a proxy+openvpn server on the other side,
it should also work.

> I don't think INDIRECT is UDP only. That is just how it prefers to be
> treated, but could be non-UDP as well, I think. (need to double check)

But for this check it is only important that the wire format is the TCP
or UDP wire format. Not the actual protocol being used.

> 
>>
>>>  
>>> +#ifdef ENABLE_PLUGIN
>>> +
>>> +int link_socket_read_indirect(struct link_socket *sock,
>>> +                              struct buffer *buf,
>>> +                              struct link_socket_actual *from);
>>> +
>>> +int link_socket_write_indirect(struct link_socket *sock,
>>> +                               struct buffer *buf,
>>> +                               struct link_socket_actual *from);
>>> +
>>> +bool proto_is_indirect(int proto);
>>> +
>>
>> This prototype definition looks a bit weird here. I see not reason why
>> the real proto_is_indirect cannot be here
> 
> you mean the proto_is_indirect() implementation should be here in the
> header? Why?

It is already in the header. Having an inline implementation AND a
prototype in the same header is what I am complaining about.


Arne

Patch

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 0a90fff0..a7092c7e 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -2150,6 +2150,11 @@  io_wait_dowork(struct context *c, const unsigned int flags)
             {
                 int i;
                 c->c2.event_set_status = 0;
+#ifdef ENABLE_PLUGIN
+                c->c2.event_set_status |=
+                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)
+                        << socket_shift;
+#endif
                 for (i = 0; i < status; ++i)
                 {
                     const struct event_set_return *e = &esr[i];
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index db944245..b548ab7a 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -41,6 +41,7 @@ 
 #include "manage.h"
 #include "openvpn.h"
 #include "forward.h"
+#include "transport.h"
 
 #include "memdbg.h"
 
@@ -49,6 +50,9 @@  const int proto_overhead[] = { /* indexed by PROTO_x */
     IPv4_UDP_HEADER_SIZE, /* IPv4 */
     IPv4_TCP_HEADER_SIZE,
     IPv4_TCP_HEADER_SIZE,
+#ifdef ENABLE_PLUGIN
+    INDIRECT_HEADER_SIZE,
+#endif
     IPv6_UDP_HEADER_SIZE, /* IPv6 */
     IPv6_TCP_HEADER_SIZE,
     IPv6_TCP_HEADER_SIZE,
@@ -1103,9 +1107,46 @@  bind_local(struct link_socket *sock, const sa_family_t ai_family)
     }
 }
 
+#ifdef ENABLE_PLUGIN
+
+static void
+create_socket_indirect(struct link_socket *sock, sa_family_t ai_family)
+{
+    struct addrinfo *bind_addresses = NULL;
+    if (sock->bind_local)
+    {
+        bind_addresses = sock->info.lsa->bind_local;
+    }
+
+    sock->indirect = transport_bind(sock->info.plugins,
+                                    sock->info.transport_plugin_argv,
+                                    ai_family,
+                                    bind_addresses);
+}
+
+bool
+proto_is_indirect(int proto)
+{
+    return proto == PROTO_INDIRECT;
+}
+
+#else  /* ifdef ENABLE_PLUGIN */
+
+static void
+create_socket_indirect(struct link_socket *sock, sa_family_t ai_family)
+{
+}
+
+#endif  /* ENABLE_PLUGIN */
+
 static void
 create_socket(struct link_socket *sock, struct addrinfo *addr)
 {
+    if (proto_is_indirect(sock->info.proto))
+    {
+        create_socket_indirect(sock, addr->ai_family);
+    }
+
     if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)
     {
         sock->sd = create_socket_udp(addr, sock->sockflags);
@@ -2279,7 +2320,11 @@  link_socket_init_phase2(struct link_socket *sock,
         }
 
         /* If socket has not already been created create it now */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )
         {
             /* If we have no --remote and have still not figured out the
              * protocol family to use we will use the first of the bind */
@@ -2300,7 +2345,11 @@  link_socket_init_phase2(struct link_socket *sock,
         }
 
         /* Socket still undefined, give a warning and abort connection */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )
         {
             msg(M_WARN, "Could not determine IPv4/IPv6 protocol");
             sig_info->signal_received = SIGUSR1;
@@ -2338,7 +2387,10 @@  link_socket_init_phase2(struct link_socket *sock,
         }
     }
 
-    phase2_set_socket_flags(sock);
+    if (sock->sd != SOCKET_UNDEFINED)
+    {
+        phase2_set_socket_flags(sock);
+    }
     linksock_print_addr(sock);
 
 done:
@@ -2362,6 +2414,14 @@  link_socket_close(struct link_socket *sock)
         const int gremlin = 0;
 #endif
 
+#ifdef ENABLE_PLUGIN
+        if (sock->indirect)
+        {
+            sock->indirect->vtab->close(sock->indirect);
+            sock->indirect = NULL;
+        }
+#endif
+
         if (socket_defined(sock->sd))
         {
 #ifdef _WIN32
@@ -3143,16 +3203,25 @@  static const struct proto_names proto_names[] = {
     {"tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER},
     {"tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT},
     {"tcp",        "TCP", AF_UNSPEC, PROTO_TCP},
+#ifdef ENABLE_PLUGIN
+    {"indirect", "INDIRECT", AF_UNSPEC, PROTO_INDIRECT},
+#endif
     /* force IPv4 */
     {"udp4",       "UDPv4", AF_INET, PROTO_UDP},
     {"tcp4-server","TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER},
     {"tcp4-client","TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT},
     {"tcp4",       "TCPv4", AF_INET, PROTO_TCP},
+#ifdef ENABLE_PLUGIN
+    {"indirect4", "INDIRECT_IPv4", AF_INET, PROTO_INDIRECT},
+#endif
     /* force IPv6 */
     {"udp6","UDPv6", AF_INET6, PROTO_UDP},
     {"tcp6-server","TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER},
     {"tcp6-client","TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT},
     {"tcp6","TCPv6", AF_INET6, PROTO_TCP},
+#ifdef ENABLE_PLUGIN
+    {"indirect6", "INDIRECT_IPv6", AF_INET6, PROTO_INDIRECT},
+#endif
 };
 
 bool
@@ -3167,6 +3236,10 @@  proto_is_net(int proto)
 bool
 proto_is_dgram(int proto)
 {
+    if (proto_is_indirect(proto))
+    {
+        return true;
+    }
     return proto_is_udp(proto);
 }
 
@@ -3301,6 +3374,18 @@  proto_remote(int proto, bool remote)
         return "TCPv4_CLIENT";
     }
 
+#ifdef ENABLE_PLUGIN
+    if (proto == PROTO_INDIRECT)
+    {
+        /* FIXME: the string reported here should match the actual transport
+         * protocol being used, however in this function we have no knowledge of
+         * what protocol is exactly being used by the transport-plugin.
+         * Therefore we simply return INDIRECT for now.
+         */
+        return "INDIRECT";
+    }
+#endif
+
     ASSERT(0);
     return ""; /* Make the compiler happy */
 }
@@ -3360,6 +3445,29 @@  link_socket_read_tcp(struct link_socket *sock,
     }
 }
 
+#ifdef ENABLE_PLUGIN
+
+int
+link_socket_read_indirect(struct link_socket *sock,
+                          struct buffer *buf,
+                          struct link_socket_actual *from)
+{
+    ASSERT(sock->indirect);
+    socklen_t fromlen = sizeof(from->dest.addr);
+    socklen_t expectedlen = af_addr_size(sock->info.af);
+    addr_zero_host(&from->dest);
+    int len = transport_read(sock->indirect, buf,
+                             &from->dest.addr.sa, &fromlen);
+    if (len >= 0 && expectedlen && fromlen != expectedlen)
+    {
+        bad_address_length(fromlen, expectedlen);
+    }
+
+    return buf->len = len;
+}
+
+#endif  /* ENABLE_PLUGIN */
+
 #ifndef _WIN32
 
 #if ENABLE_IP_PKTINFO
@@ -3492,6 +3600,21 @@  link_socket_write_tcp(struct link_socket *sock,
 #endif
 }
 
+#ifdef ENABLE_PLUGIN
+
+int
+link_socket_write_indirect(struct link_socket *sock,
+                           struct buffer *buf,
+                           struct link_socket_actual *to)
+{
+    ASSERT(sock->indirect);
+    struct sockaddr *addr = (struct sockaddr *) &to->dest.addr.sa;
+    socklen_t addrlen = (socklen_t) af_addr_size(to->dest.addr.sa.sa_family);
+    return transport_write(sock->indirect, buf, addr, addrlen);
+}
+
+#endif  /* ENABLE_PLUGIN */
+
 #if ENABLE_IP_PKTINFO
 
 size_t
@@ -3580,6 +3703,12 @@  link_socket_write_udp_posix_sendmsg(struct link_socket *sock,
 int
 socket_recv_queue(struct link_socket *sock, int maxsize)
 {
+    if (proto_is_indirect(sock->info.proto))
+    {
+        /* Indirect handler will take care of this, so do nothing. */
+        return IOSTATE_QUEUED;
+    }
+
     if (sock->reads.iostate == IOSTATE_INITIAL)
     {
         WSABUF wsabuf[1];
@@ -3952,7 +4081,16 @@  socket_set(struct link_socket *s,
         /* if persistent is defined, call event_ctl only if rwflags has changed since last call */
         if (!persistent || *persistent != rwflags)
         {
-            event_ctl(es, socket_event_handle(s), rwflags, arg);
+#ifdef ENABLE_PLUGIN
+            if (s->indirect)
+            {
+                transport_request_events(s->indirect, es, rwflags);
+            }
+            else
+#endif
+            {
+                event_ctl(es, socket_event_handle(s), rwflags, arg);
+            }
             if (persistent)
             {
                 *persistent = rwflags;
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index f49e6315..73a4ab6f 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -34,6 +34,7 @@ 
 #include "proxy.h"
 #include "socks.h"
 #include "misc.h"
+#include "transport.h"
 
 /*
  * OpenVPN's default port number as assigned by IANA.
@@ -115,6 +116,7 @@  struct link_socket_info
     bool connection_established;
     const char *ipchange_command;
     const struct plugin_list *plugins;
+    const char **transport_plugin_argv;
     bool remote_float;
     int proto;                  /* Protocol (PROTO_x defined below) */
     sa_family_t af;                     /* Address family like AF_INET, AF_INET6 or AF_UNSPEC*/
@@ -175,6 +177,11 @@  struct link_socket
     struct rw_handle listen_handle; /* For listening on TCP socket in server mode */
 #endif
 
+#ifdef ENABLE_PLUGIN
+    /* only valid when info.proto == PROTO_INDIRECT */
+    openvpn_transport_socket_t indirect;
+#endif
+
     /* used for printing status info only */
     unsigned int rwflags_debug;
 
@@ -1049,12 +1056,53 @@  int link_socket_read_udp_posix(struct link_socket *sock,
 
 #endif
 
+#ifdef ENABLE_PLUGIN
+
+int link_socket_read_indirect(struct link_socket *sock,
+                              struct buffer *buf,
+                              struct link_socket_actual *from);
+
+int link_socket_write_indirect(struct link_socket *sock,
+                               struct buffer *buf,
+                               struct link_socket_actual *from);
+
+bool proto_is_indirect(int proto);
+
+#else  /* ifdef ENABLE_PLUGIN */
+
+static int
+link_socket_read_indirect(struct link_socket *sock,
+                          struct buffer *buf, struct link_socket_actual *from)
+{
+    return -1;
+}
+
+static int
+link_socket_write_indirect(struct link_socket *sock,
+                           struct buffer *buf, struct link_socket_actual *from)
+{
+    return -1;
+}
+
+static bool
+proto_is_indirect(int proto)
+{
+    return false;
+}
+
+#endif  /* ENABLE_PLUGIN */
+
 /* read a TCP or UDP packet from link */
 static inline int
 link_socket_read(struct link_socket *sock,
                  struct buffer *buf,
                  struct link_socket_actual *from)
 {
+    if (proto_is_indirect(sock->info.proto))
+    {
+        return link_socket_read_indirect(sock, buf, from);
+    }
+
     if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
     {
         int res;
@@ -1169,6 +1217,11 @@  link_socket_write(struct link_socket *sock,
                   struct buffer *buf,
                   struct link_socket_actual *to)
 {
+    if (proto_is_indirect(sock->info.proto))
+    {
+        return link_socket_write_indirect(sock, buf, to);
+    }
+
     if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
     {
         return link_socket_write_udp(sock, buf, to);
@@ -1264,6 +1317,23 @@  socket_reset_listen_persistent(struct link_socket *s)
 #endif
 }
 
+#ifdef ENABLE_PLUGIN
+
+static inline unsigned
+socket_indirect_pump(struct link_socket *s, struct event_set_return *esr, int *esrlen)
+{
+    if (s->indirect)
+    {
+        return transport_pump(s->indirect, esr, esrlen);
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+#endif  /* ENABLE_PLUGIN */
+
 const char *socket_stat(const struct link_socket *s, unsigned int rwflags, struct gc_arena *gc);
 
 #endif /* SOCKET_H */
diff --git a/src/openvpn/transport.h b/src/openvpn/transport.h
index 344ce44b..37050eaf 100644
--- a/src/openvpn/transport.h
+++ b/src/openvpn/transport.h
@@ -27,6 +27,11 @@ 
 #include "plugin.h"
 #include "openvpn-transport.h"
 
+/* INDIRECT does not have any overhead per se, but it depends on what is
+ * implemented by the transport plugin
+ */
+#define INDIRECT_HEADER_SIZE    0
+
 /* Given a list of plugins and an argument list for a desired
  * transport plugin instance, prepare to bind new link sockets using
  * that transport plugin and args. If all succeeds, return true, and: