Hi Alec,

Overall it looks nice. I didn't know that SOCKS4 only included a header
in each direction, thus of course your approach is simpler than creating
a new mux.

I do however have some comments, mainly about the way used to retrieve
configuration elements as at the moment it doubles the memory consumption
of each connection, but there are solutions to fix this that I describe
below.

> diff --git a/doc/SOCKS4.protocol.txt b/doc/SOCKS4.protocol.txt
> new file mode 100644
> index 00000000..16483dee
> --- /dev/null
> +++ b/doc/SOCKS4.protocol.txt
> @@ -0,0 +1,150 @@
> +     SOCKS: A protocol for TCP proxy across firewalls
> +
> +                     Ying-Da Lee
> +             yin...@best.com  or  yin...@esd.sgi.com

(...)

I'm seeing that you copied a doc retrieved from somewhere else that is
found at various places on the net. Have you checked the license for
this doc to be sure we can copy and distribute it like this ? It might
be perfectly fine but we need to be sure. In any case we'll have to
mention the license if it differs from the other ones.

> diff --git a/examples/socks4.cfg b/examples/socks4.cfg
> new file mode 100644
> index 00000000..22cbad44
> --- /dev/null
> +++ b/examples/socks4.cfg
> (...)
> +     server SMTPS1_Via_SocksProxy1 169.38.103.42:25   check inter 30000 
> fastinter 1000
> +     server SMTPS2_Via_SocksProxy2 161.202.148.179:25 socks4 127.0.0.1:1080 
> check-via-socks check inter 30000 fastinter 1000 backup

These ones are real mail servers which do respond on the net, it's not
really friendly nor recommended to put real server addresses there, you
can use the 192.0.2.0/24 range instead, which is reserved for
documentation.

> diff --git a/include/types/checks.h b/include/types/checks.h
> index f89abcba..b09b5f89 100644
> --- a/include/types/checks.h
> +++ b/include/types/checks.h
> @@ -189,6 +189,8 @@ struct check {
>       char *sni;                              /* Server name */
>       char *alpn_str;                         /* ALPN to use for checks */
>       int alpn_len;                           /* ALPN string length */
> +
> +     int via_socks;                          /* check the connection via 
> socks proxy */
>  };

At some point we'll have to think about creating a bit field for all
these booleans because we're progressively inflating the struct by
32 bits every time we need a new flag. But let's do this later and
not annoy you with this for now.
 
>  struct check_status {
> diff --git a/include/types/connection.h b/include/types/connection.h
> index 308be7d7..2a1b23b4 100644
> --- a/include/types/connection.h
> +++ b/include/types/connection.h
> @@ -47,6 +47,15 @@ struct server;
>  struct session;
>  struct pipe;
>  
> +/* socks4 upstream proxy definitions */
> +struct socks4_request {
> +     uint8_t version;        /* SOCKS version number, 1 byte, must be 0x04 
> for this version */
> +     uint8_t command;        /* 0x01 = establish a TCP/IP stream connection 
> */
> +     uint16_t port;          /* port number, 2 bytes (in network byte order) 
> */
> +     uint32_t ip;            /* IP address, 4 bytes (in network byte order) 
> */
> +     char user_id[8];        /* the user ID string, variable length, 
> terminated with a null (0x00); Using "HAProxy\0" */
> +};
> +
>  /* Note: subscribing to these events is only valid after the caller has 
> really
>   * attempted to perform the operation, and failed to proceed or complete.
>   */
> @@ -156,7 +165,7 @@ enum {
>  
>       CO_FL_EARLY_SSL_HS  = 0x00004000,  /* We have early data pending, don't 
> start SSL handshake yet */
>       CO_FL_EARLY_DATA    = 0x00008000,  /* At least some of the data are 
> early data */
> -     /* unused : 0x00010000 */
> +     CO_FL_SOCKS_HS      = 0x00010000,  /* handshaking with upstream SOCKS 
> proxy */
>       /* unused : 0x00020000 */

>From what I'm seeing later in the code, it's not exactly a handshake,
you have to send a header and to wait for the response header to arrive.
I'd rather have one flag per direction which save from having to deal
with state management.

> +/* flags for use in connection->socks_proxy.status */
> +enum {
> +     CO_SOCKS_NONE,
> +     CO_SOCKS_SENDING,
> +     CO_SOCKS_SENT,
> +     CO_SOCKS_RECEIVING,
> +     CO_SOCKS_READY,
> +     CO_SOCKS_DENY
> +};

In my opinion these ones are easily addressed with 2 bits as mentioned
above. The error status can be used for the deny case upon receipt.
 
>  /* possible connection error codes */
>  enum {
> @@ -451,6 +469,13 @@ struct connection {
>       struct {
>               struct sockaddr_storage from;   /* client address, or address 
> to spoof when connecting to the server */
>               struct sockaddr_storage to;     /* address reached by the 
> client, or address to connect to */
> +             struct {
> +                     unsigned char use;                      /* socks4 proxy 
> enabled  */
> +                     struct sockaddr_storage addr;           /* the address 
> of the socks4 proxy */
> +                     struct socks4_request req_line;         /* the info 
> send to socks4 proxy */
> +                     unsigned int status;                    /* CO_SOCKS_* */
> +                     signed short req_remain_len;            /* the length 
> remain to sent */
> +             } socks_proxy;

It is extremely huge for the struct connection unfortunately. We're
currently trying to shrink it, ideally extracting the address field
which is large, and this doubles it. With the two handshake flags
you can cover the status. The "use" has to be found and enabled
based on the server, and the address is fixed so it can also be passed
from the server. The request line could be rebuilt on each attempt as
we do with the proxy protocol header. Maybe the only part remaining
would be req_remain_len, which could probably be merged with the
proxy protocol offset later since we don't need to maintain both at
once. If you can use the PP offset now it's even better ;-)

>  /*
>   * Linux seems to be able to send 253 fds per sendmsg(), not sure
>   * about the other OSes.
> diff --git a/include/types/server.h b/include/types/server.h
> index 7835f11c..7a383d44 100644
> --- a/include/types/server.h
> +++ b/include/types/server.h
> @@ -338,6 +338,9 @@ struct server {
>               char reason[128];
>       } op_st_chg;                            /* operational status change's 
> reason */
>       char adm_st_chg_cause[48];              /* administrative status 
> change's cause */
> +
> +     unsigned char use_socks4;               /* socks4 proxy enabled  */

There seems to be plenty of unused flags left in the server flags, look
at SRV_F_USE_NS_FROM_PP for example to see how to get another one. Also
and generally speaking, please be careful about alignment, as just
placing one char above, followed by a 8- or 16-byte aligned struct results
in a 7- or 15- bytes hole, and all these holes do have a cost on memory
usage, and performance via cache line thrashing.

> +     struct sockaddr_storage socks4_addr;    /* the address of the socks4 
> proxy, including the port */
>  };
>  
>  /* Descriptor for a "server" keyword. The ->parse() function returns 0 in 
> case of
> diff --git a/src/backend.c b/src/backend.c
> index d7695bf2..52b74e7c 100644
> --- a/src/backend.c
> +++ b/src/backend.c
> @@ -888,6 +888,18 @@ int assign_server_address(struct stream *s, struct 
> connection *srv_conn)
>               srv_conn->addr.to = __objt_server(s->target)->addr;
>               set_host_port(&srv_conn->addr.to, 
> __objt_server(s->target)->svc_port);
>  
> +             if (__objt_server(s->target)->use_socks4) {
> +                     srv_conn->addr.socks_proxy.use = 1;
> +                     srv_conn->addr.socks_proxy.addr = 
> __objt_server(s->target)->socks4_addr;
> +                     srv_conn->addr.socks_proxy.status = CO_SOCKS_NONE;
> +                     srv_conn->addr.socks_proxy.req_line.version = 0x04;
> +                     srv_conn->addr.socks_proxy.req_line.command = 0x01;
> +                     srv_conn->addr.socks_proxy.req_line.port = 
> get_net_port(&(srv_conn->addr.to));
> +                     srv_conn->addr.socks_proxy.req_line.ip = 
> is_inet_addr(&(srv_conn->addr.to));
> +                     strcpy(srv_conn->addr.socks_proxy.req_line.user_id, 
> "HAProxy");
> +                     srv_conn->addr.socks_proxy.req_remain_len = 
> sizeof(srv_conn->addr.socks_proxy.req_line);
> +             }

So for me here instead you should just set on the connection the flags
for the send and the recv part of the SOCKS4 protocol, and set the send
offset to 0 (for example), so that the function in charge for sending
the header knows where to restart from. All the rest is available from
these functions :
  - the server struct that you need to retrieve the socks4 address
    and port is accessible via objt_server(conn->target)
  - the constants (version, command, user_id, ...) can be hard-coded there
  - the req_line's ip/port can be taken extracted from conn->addr.to
    in these functions like you do here

Thus none of these fields needs to be set here in the end (which is a
huge memory saver).

> diff --git a/src/checks.c b/src/checks.c
> index 76bd8e3d..2809fbe2 100644
> --- a/src/checks.c
> +++ b/src/checks.c
> @@ -1611,6 +1611,25 @@ static int connect_conn_chk(struct task *t)
>               conn->addr.to = s->addr;
>       }
>  
> +     if (s->check.via_socks) {
> +             if (s->use_socks4) {
> +                     conn->addr.socks_proxy.use = 1;
> +                     conn->addr.socks_proxy.addr = s->socks4_addr;
> +                     conn->addr.socks_proxy.status = CO_SOCKS_NONE;
> +                     conn->addr.socks_proxy.req_line.version = 0x04;
> +                     conn->addr.socks_proxy.req_line.command = 0x01;
> +                     conn->addr.socks_proxy.req_line.port = 
> get_net_port(&(conn->addr.to));
> +                     conn->addr.socks_proxy.req_line.ip = 
> is_inet_addr(&(conn->addr.to));
> +                     strcpy(conn->addr.socks_proxy.req_line.user_id, 
> "HAProxy");
> +                     conn->addr.socks_proxy.req_remain_len = 
> sizeof(conn->addr.socks_proxy.req_line);
> +
> +                     conn->flags |= CO_FL_SOCKS_HS;
> +             }

Exact same principle here.

> diff --git a/src/connection.c b/src/connection.c
> index 2a66996b..b7ec820c 100644
> --- a/src/connection.c
> +++ b/src/connection.c
> @@ -25,6 +25,7 @@
>  #include <proto/proto_tcp.h>
>  #include <proto/stream_interface.h>
>  #include <proto/sample.h>
> +#include <proto/log.h>
>  
>  #ifdef USE_OPENSSL
>  #include <proto/ssl_sock.h>
> @@ -72,6 +73,21 @@ void conn_fd_handler(int fd)
>               if (unlikely(conn->flags & CO_FL_ERROR))
>                       goto leave;
>  
> +             if (conn->flags & CO_FL_SOCKS_HS) {
> +                     if (conn->addr.socks_proxy.status == CO_SOCKS_NONE || 
> conn->addr.socks_proxy.status == CO_SOCKS_SENDING) {
> +                             //Going to send the request to the socks4 proxy
> +                             conn_send_socks_proxy_request(conn);
> +                             goto leave;
> +                     }
> +
> +                     if (conn->addr.socks_proxy.status == CO_SOCKS_SENT || 
> conn->addr.socks_proxy.status == CO_SOCKS_RECEIVING) {
> +                             //Check the response from the socks4 proxy
> +                             if (!conn_recv_socks_proxy_response(conn, 
> CO_FL_SOCKS_HS)) {
> +                                     goto leave;
> +                             }
> +                     }
> +             }
> +

So here instead of coding the state machine there based on the status,
you can simply call each individual function depending on a single flag
for each (say CO_FL_SEND_SOCKS4, CO_FL_RECV_SOCKS4).

> @@ -966,6 +982,145 @@ int conn_recv_netscaler_cip(struct connection *conn, 
> int flag)
>       return 0;
>  }
>  
> +
> +int conn_send_socks_proxy_request(struct connection *conn)
> +{
> +     /* we might have been called just after an asynchronous shutw */
> +     if (conn->flags & CO_FL_SOCK_WR_SH)
> +             goto out_error;
> +
> +     if (!conn_ctrl_ready(conn))
> +             goto out_error;
> +

Here you'd start by rebuilding the request line into a temporary local
buffer based on the same elements you used for the checks and for the
server's connect. You know the value is stable over time so you can
redo it and it will give you the same result. You just have to restart
sending from the send offset.

> +     while (conn->addr.socks_proxy.req_remain_len) {
> +             int ret = 0;
> +
> +             conn->addr.socks_proxy.status = CO_SOCKS_SENDING;
> +
> +             /* we are sending the socks4_req_line here. If the data layer
> +              * has a pending write, we'll also set MSG_MORE.
> +              */
> +             ret = conn_sock_send(conn,
> +                                  ((char 
> *)(&(conn->addr.socks_proxy.req_line))) +
> +                                  
> (sizeof(conn->addr.socks_proxy.req_line)-(conn->addr.socks_proxy.req_remain_len)),
> +                                  conn->addr.socks_proxy.req_remain_len,
> +                                  (conn->flags & CO_FL_XPRT_WR_ENA) ? 
> MSG_MORE : 0);
> +
> +             ha_alert("SOCKS PROXY HS FD[%04X]: Before send remain is [%d], 
> sent [%d]\n", conn->handle.fd, conn->addr.socks_proxy.req_remain_len, ret);

By the way, I'm assuming that for now this is for debugging, but I just
wanted you to be aware that ha_alert() and ha_warning() are only meant
to be used during config parsing and not run time as they will most
often not be visible due to stdout/stderr being closed and/or redirected
to something else. If you need to emit some errors at run time, please
raise CO_FL_ERROR and set an error code among CO_ER_* into
conn->err_code. You may add other codes but then please add the human
readable version into proto/connection.h (grep for CO_ER_SSL_RENEG for
example).

> +int conn_recv_socks_proxy_response(struct connection *conn, unsigned int 
> flag)
> +{
(...)
> +     read_count = 0;
> +     do {
> +             /* SOCKS4 Proxy request granted server response, 0x00 | 0x5A | 
> 0x00 0x00 | 0x00 0x00 0x00 0x00
> +              * Try to peek into it, before all 8 bytes of response ready.
> +              */
> +             ret = recv(conn->handle.fd, line+read_count, 8-read_count, 
> MSG_PEEK);

Please use sizeof(line) instead of the hard-coded 8 here. I can
guarantee you that your code will end up being copy-pasted or updated
and that someone will end up with something larger than 8 at one point
and you'll get 8-something resulting in an integer overflow for the
limit.

> +             if (ret > 0) {
> +                     conn->addr.socks_proxy.status = CO_SOCKS_RECEIVING;
> +                     if (ret == 8){

Same here.
(...)

> +             if (ret < 0) {
> +                     if (errno == EINTR) {
> +                             continue;
> +                     }
> +                     if (errno == EAGAIN) {
> +                             fd_cant_recv(conn->handle.fd);
> +                             return 0;
> +                     }
> +                     goto fail;
> +             } else {
> +                     read_count += ret;
> +             }
> +     } while (read_count < 8);

This will loop forever due to MSG_PEEK. You must not loop when doing
this. EAGAIN will only be returned as long as there is zero byte in,
but as soon as you have between 1 and 7 bytes you'll be stuck there.
Better simply return and deal with this just like EAGAIN. I think it
is what is already done in the PP/CIP receive code.

> +     if (line[1] != 0x5A) {

Would be nice to have this 0x5A defined somewhere as a more or less
explicit macro or enum so that whoever reads the code has an idea what
it's supposed to mean exactly. We've had tons of hard-coded values in
the peers code and it's one of the most undebuggable parts of the code.

> +             conn->addr.socks_proxy.status = CO_SOCKS_DENY;
> +             conn->flags &= ~flag;

So here it could be turned to CO_FL_ERROR with CO_ER_SOCKS_DENY for
example.

> +     /* remove the 8 bytes response from the stream */
> +     do {
> +             ret = recv(conn->handle.fd, line, 8, 0);

Just as a reminder, "8" spotted here.

> +             if (ret < 0 && errno == EINTR) {
> +                     continue;
> +             }
> +             if (ret != 8) {

and here.

> diff --git a/src/proto_tcp.c b/src/proto_tcp.c
> index d4cad223..54949533 100644
> --- a/src/proto_tcp.c
> +++ b/src/proto_tcp.c
> @@ -293,6 +293,7 @@ int tcp_connect_server(struct connection *conn, int data, 
> int delack)
>       struct server *srv;
>       struct proxy *be;
>       struct conn_src *src;
> +     int ret;
>  
>       conn->flags |= CO_FL_WAIT_L4_CONN; /* connection in progress */
>  
> @@ -496,7 +497,12 @@ int tcp_connect_server(struct connection *conn, int 
> data, int delack)
>       if (global.tune.server_rcvbuf)
>                  setsockopt(fd, SOL_SOCKET, SO_RCVBUF, 
> &global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
>  
> -     if (connect(fd, (struct sockaddr *)&conn->addr.to, 
> get_addr_len(&conn->addr.to)) == -1) {
> +     if (conn->addr.socks_proxy.use) {
> +             ret = connect(fd, (struct sockaddr 
> *)&conn->addr.socks_proxy.addr, get_addr_len(&conn->addr.socks_proxy.addr));
> +     } else {
> +             ret = connect(fd, (struct sockaddr *)&conn->addr.to, 
> get_addr_len(&conn->addr.to));
> +     }

This is where you can use the connection's flags instead :

        const struct sockaddr_storage *addr = &conn->addr.to;

        (...)
        if (conn->flags & CO_FL_SEND_SOCKS4 && srv)
                addr = &srv->socks4_addr;
        if (connect(fd, (struct sockaddr *)addr, get_addr_len(addr)) == -1) {
        (...)

And that's about all. I think that the design is light enough to be
merged, I'm not seeing any significant showtopper.

Thanks!
Willy

Reply via email to