Hi all, For my needs, I've updated the sendproxy patch for stunnel 4.34 and prepared another one to backport the PROXY protocol in haproxy 1.4.10.
Maybe it can interest someone else than me. -- Cyril Bonté
diff -ru haproxy-1.4.10/doc/configuration.txt haproxy-1.4.10-accept-proxy/doc/configuration.txt --- haproxy-1.4.10/doc/configuration.txt 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/doc/configuration.txt 2010-12-07 22:31:24.721186953 +0100 @@ -1318,6 +1318,7 @@ bind [<address>]:<port_range> [, ...] id <id> bind [<address>]:<port_range> [, ...] name <name> bind [<address>]:<port_range> [, ...] defer-accept +bind [<address>]:<port_range> [, ...] accept-proxy Define one or several listening addresses and/or ports in a frontend. May be used in sections : defaults | frontend | listen | backend no | yes | yes | no @@ -1398,6 +1399,19 @@ with front firewalls which would see an established connection while the proxy will only see it in SYN_RECV. + accept-proxy is an optional keyword which enforces use of the PROXY + protocol over any connection accepted by this listener. The + PROXY protocol dictates the layer 3/4 addresses of the + incoming connection to be used everywhere an address is used, + with the only exception of "tcp-request connection" rules + which will only see the real connection address. Logs will + reflect the addresses indicated in the protocol, unless it is + violated, in which case the real address will still be used. + This keyword combined with support from external components + can be used as an efficient and reliable alternative to the + X-Forwarded-For mechanism which is not always reliable and + not even always usable. + It is possible to specify a list of address:port combinations delimited by commas. The frontend will then listen on all of these addresses. There is no fixed limit to the number of addresses and ports which can be listened on in @@ -1408,8 +1422,10 @@ listen http_proxy bind :80,:443 bind 10.0.0.1:10080,10.0.0.1:10443 + bind 127.0.0.1:8443 accept-proxy - See also : "source". + See also : "source", "option forwardfor" and the PROXY protocol + documentation. bind-process [ all | odd | even | <number 1-32> ] ... @@ -7116,7 +7132,9 @@ Detailed fields description : - "client_ip" is the IP address of the client which initiated the TCP - connection to haproxy. + connection to haproxy. Note that when the connection is accepted on a + socket configured with "accept-proxy" and the PROXY protocol is correctly + used, then the logs will reflect the forwarded connection's information. - "client_port" is the TCP port of the client which initiated the connection. @@ -7289,7 +7307,9 @@ Detailed fields description : - "client_ip" is the IP address of the client which initiated the TCP - connection to haproxy. + connection to haproxy. Note that when the connection is accepted on a + socket configured with "accept-proxy" and the PROXY protocol is correctly + used, then the logs will reflect the forwarded connection's information. - "client_port" is the TCP port of the client which initiated the connection. diff -ru haproxy-1.4.10/include/common/standard.h haproxy-1.4.10-accept-proxy/include/common/standard.h --- haproxy-1.4.10/include/common/standard.h 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/include/common/standard.h 2010-12-07 22:00:46.959888470 +0100 @@ -262,6 +262,28 @@ return i; } +/* This function reads an unsigned integer from the string pointed to by <s> + * and returns it. The <s> pointer is adjusted to point to the first unread + * char. The function automatically stops at <end>. + */ +static inline unsigned int __read_uint(const char **s, const char *end) +{ + const char *ptr = *s; + unsigned int i = 0; + unsigned int j, k; + + while (ptr < end) { + j = *ptr - '0'; + k = i * 10; + if (j > 9) + break; + i = k + j; + ptr++; + } + *s = ptr; + return i; +} + extern unsigned int str2ui(const char *s); extern unsigned int str2uic(const char *s); extern unsigned int strl2ui(const char *s, int len); @@ -269,6 +291,7 @@ extern int strl2ic(const char *s, int len); extern int strl2irc(const char *s, int len, int *ret); extern int strl2llrc(const char *s, int len, long long *ret); +extern unsigned int read_uint(const char **s, const char *end); unsigned int inetaddr_host(const char *text); unsigned int inetaddr_host_lim(const char *text, const char *stop); unsigned int inetaddr_host_lim_ret(const char *text, char *stop, const char **ret); diff -ru haproxy-1.4.10/include/proto/client.h haproxy-1.4.10-accept-proxy/include/proto/client.h --- haproxy-1.4.10/include/proto/client.h 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/include/proto/client.h 2010-12-07 21:56:37.529000821 +0100 @@ -25,6 +25,7 @@ #include <common/config.h> #include <types/session.h> +int frontend_decode_proxy_request(struct session *s, struct buffer *req, int an_bit); void get_frt_addr(struct session *s); int event_accept(int fd); diff -ru haproxy-1.4.10/include/types/buffers.h haproxy-1.4.10-accept-proxy/include/types/buffers.h --- haproxy-1.4.10/include/types/buffers.h 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/include/types/buffers.h 2010-12-07 21:53:48.631039766 +0100 @@ -134,16 +134,16 @@ * The field is blanked by buffer_init() and only by analysers themselves * afterwards. */ -#define AN_REQ_INSPECT 0x00000001 /* inspect request contents */ -#define AN_REQ_WAIT_HTTP 0x00000002 /* wait for an HTTP request */ -#define AN_REQ_HTTP_PROCESS_FE 0x00000004 /* process the frontend's HTTP part */ -#define AN_REQ_SWITCHING_RULES 0x00000008 /* apply the switching rules */ -#define AN_REQ_HTTP_PROCESS_BE 0x00000010 /* process the backend's HTTP part */ -#define AN_REQ_HTTP_INNER 0x00000020 /* inner processing of HTTP request */ -#define AN_REQ_HTTP_TARPIT 0x00000040 /* wait for end of HTTP tarpit */ -#define AN_REQ_HTTP_BODY 0x00000080 /* inspect HTTP request body */ -#define AN_REQ_STICKING_RULES 0x00000100 /* table persistence matching */ -/* unused: 0x200 */ +#define AN_REQ_DECODE_PROXY 0x00000001 /* take the proxied address from a 'PROXY' line */ +#define AN_REQ_INSPECT 0x00000002 /* inspect request contents */ +#define AN_REQ_WAIT_HTTP 0x00000004 /* wait for an HTTP request */ +#define AN_REQ_HTTP_PROCESS_FE 0x00000008 /* process the frontend's HTTP part */ +#define AN_REQ_SWITCHING_RULES 0x00000010 /* apply the switching rules */ +#define AN_REQ_HTTP_PROCESS_BE 0x00000020 /* process the backend's HTTP part */ +#define AN_REQ_HTTP_INNER 0x00000040 /* inner processing of HTTP request */ +#define AN_REQ_HTTP_TARPIT 0x00000080 /* wait for end of HTTP tarpit */ +#define AN_REQ_HTTP_BODY 0x00000100 /* inspect HTTP request body */ +#define AN_REQ_STICKING_RULES 0x00000200 /* table persistence matching */ #define AN_REQ_PRST_RDP_COOKIE 0x00000400 /* persistence on rdp cookie */ #define AN_REQ_HTTP_XFER_BODY 0x00000800 /* forward request body */ diff -ru haproxy-1.4.10/include/types/protocols.h haproxy-1.4.10-accept-proxy/include/types/protocols.h --- haproxy-1.4.10/include/types/protocols.h 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/include/types/protocols.h 2010-12-07 22:04:50.514700235 +0100 @@ -72,6 +72,7 @@ #define LI_O_FOREIGN 0x0002 /* permit listening on foreing addresses */ #define LI_O_NOQUICKACK 0x0004 /* disable quick ack of immediate data (linux) */ #define LI_O_DEF_ACCEPT 0x0008 /* wait up to 1 second for data before accepting */ +#define LI_O_ACC_PROXY 0x0010 /* find the proxied address in the first request line */ /* The listener will be directly referenced by the fdtab[] which holds its * socket. The listener provides the protocol-specific accept() function to diff -ru haproxy-1.4.10/src/cfgparse.c haproxy-1.4.10-accept-proxy/src/cfgparse.c --- haproxy-1.4.10/src/cfgparse.c 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/src/cfgparse.c 2010-12-07 22:08:24.284338273 +0100 @@ -1413,6 +1413,16 @@ #endif } + if (!strcmp(args[cur_arg], "accept-proxy")) { /* expect a 'PROXY' line first */ + struct listener *l; + + for (l = curproxy->listen; l != last_listen; l = l->next) + l->options |= LI_O_ACC_PROXY; + + cur_arg ++; + continue; + } + if (!strcmp(args[cur_arg], "name")) { struct listener *l; @@ -1465,7 +1475,7 @@ continue; } - Alert("parsing [%s:%d] : '%s' only supports the 'transparent', 'defer-accept', 'name', 'id', 'mss' and 'interface' options.\n", + Alert("parsing [%s:%d] : '%s' only supports the 'transparent', 'accept-proxy', 'defer-accept', 'name', 'id', 'mss' and 'interface' options.\n", file, linenum, args[0]); err_code |= ERR_ALERT | ERR_FATAL; goto out; @@ -5621,6 +5631,9 @@ listener->handler = process_session; listener->analysers |= curproxy->fe_req_ana; + if (listener->options & LI_O_ACC_PROXY) + listener->analysers |= AN_REQ_DECODE_PROXY; + /* smart accept mode is automatic in HTTP mode */ if ((curproxy->options2 & PR_O2_SMARTACC) || (curproxy->mode == PR_MODE_HTTP && diff -ru haproxy-1.4.10/src/client.c haproxy-1.4.10-accept-proxy/src/client.c --- haproxy-1.4.10/src/client.c 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/src/client.c 2010-12-07 21:58:15.502136150 +0100 @@ -22,6 +22,7 @@ #include <common/compat.h> #include <common/config.h> +#include <common/debug.h> #include <common/time.h> #include <types/global.h> @@ -43,6 +44,191 @@ #include <proto/task.h> +/* This analyser tries to fetch a line from the request buffer which looks like : + * + * "PROXY" <SP> PROTO <SP> SRC3 <SP> DST3 <SP> SRC4 <SP> <DST4> "\r\n" + * + * There must be exactly one space between each field. Fields are : + * - PROTO : layer 4 protocol, which must be "TCP4" or "TCP6". + * - SRC3 : layer 3 (eg: IP) source address in standard text form + * - DST3 : layer 3 (eg: IP) destination address in standard text form + * - SRC4 : layer 4 (eg: TCP port) source address in standard text form + * - DST4 : layer 4 (eg: TCP port) destination address in standard text form + * + * This line MUST be at the beginning of the buffer and MUST NOT wrap. + * + * Once the data is fetched, the values are set in the session's field and data + * are removed from the buffer. The function returns zero if it needs to wait + * for more data (max: timeout_client), or 1 if it has finished and removed itself. + */ +int frontend_decode_proxy_request(struct session *s, struct buffer *req, int an_bit) +{ + char *line = req->data; + char *end = req->data + req->l; + int len; + + DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n", + now_ms, __FUNCTION__, + s, + req, + req->rex, req->wex, + req->flags, + req->l, + req->analysers); + + if (req->flags & (BF_READ_ERROR|BF_READ_TIMEOUT)) + goto fail; + + len = MIN(req->l, 6); + if (!len) + goto missing; + + /* Decode a possible proxy request, fail early if it does not match */ + if (strncmp(line, "PROXY ", len) != 0) + goto fail; + + line += 6; + if (req->l < 18) /* shortest possible line */ + goto missing; + + if (!memcmp(line, "TCP4 ", 5) != 0) { + u32 src3, dst3, sport, dport; + + line += 5; + + src3 = inetaddr_host_lim_ret(line, end, &line); + if (line == end) + goto missing; + if (*line++ != ' ') + goto fail; + + dst3 = inetaddr_host_lim_ret(line, end, &line); + if (line == end) + goto missing; + if (*line++ != ' ') + goto fail; + + sport = read_uint((const char **)&line, end); + if (line == end) + goto missing; + if (*line++ != ' ') + goto fail; + + dport = read_uint((const char **)&line, end); + if (line > end - 2) + goto missing; + if (*line++ != '\r') + goto fail; + if (*line++ != '\n') + goto fail; + + /* update the session's addresses and mark them set */ + ((struct sockaddr_in *)&s->cli_addr)->sin_family = AF_INET; + ((struct sockaddr_in *)&s->cli_addr)->sin_addr.s_addr = htonl(src3); + ((struct sockaddr_in *)&s->cli_addr)->sin_port = htons(sport); + + ((struct sockaddr_in *)&s->frt_addr)->sin_family = AF_INET; + ((struct sockaddr_in *)&s->frt_addr)->sin_addr.s_addr = htonl(dst3); + ((struct sockaddr_in *)&s->frt_addr)->sin_port = htons(dport); + s->flags |= SN_FRT_ADDR_SET; + + } + else if (!memcmp(line, "TCP6 ", 5) != 0) { + u32 sport, dport; + char *src_s; + char *dst_s, *sport_s, *dport_s; + struct in6_addr src3, dst3; + + line+=5; + + src_s = line; + dst_s = sport_s = dport_s = NULL; + while (1) { + if (line > end - 2) { + goto missing; + } + else if (*line == '\r') { + *line = 0; + line++; + if (*line++ != '\n') + goto fail; + break; + } + + if (*line == ' ') { + *line = 0; + if (!dst_s) + dst_s = line+1; + else if (!sport_s) + sport_s = line+1; + else if (!dport_s) + dport_s = line+1; + } + line++; + } + + if (!dst_s || !sport_s || !dport_s) + goto fail; + + sport = read_uint((const char **)&sport_s,dport_s-1); + if ( *sport_s != 0 ) + goto fail; + + dport = read_uint((const char **)&dport_s,line-2); + if ( *dport_s != 0 ) + goto fail; + + if (inet_pton(AF_INET6, src_s, (void *)&src3) != 1) + goto fail; + + if (inet_pton(AF_INET6, dst_s, (void *)&dst3) != 1) + goto fail; + + /* update the session's addresses and mark them set */ + ((struct sockaddr_in6 *)&s->cli_addr)->sin6_family = AF_INET6; + memcpy(&((struct sockaddr_in6 *)&s->cli_addr)->sin6_addr, &src3, sizeof(struct in6_addr)); + ((struct sockaddr_in6 *)&s->cli_addr)->sin6_port = htons(sport); + + ((struct sockaddr_in6 *)&s->frt_addr)->sin6_family = AF_INET6; + memcpy(&((struct sockaddr_in6 *)&s->frt_addr)->sin6_addr, &dst3, sizeof(struct in6_addr)); + ((struct sockaddr_in6 *)&s->frt_addr)->sin6_port = htons(dport); + s->flags |= SN_FRT_ADDR_SET; + } + else { + goto fail; + } + + /* remove the PROXY line from the request */ + len = line - req->data; + buffer_replace2(req, req->data, line, NULL, 0); + req->total -= len; /* don't count the header line */ + + req->analysers &= ~an_bit; + return 1; + + missing: + if (!(req->flags & (BF_SHUTR|BF_FULL))) { + buffer_dont_connect(s->req); + return 0; + } + /* missing data and buffer is either full or shutdown => fail */ + + fail: + buffer_abort(req); + buffer_abort(s->rep); + req->analysers = 0; + + s->fe->counters.failed_req++; + if (s->listener->counters) + s->listener->counters->failed_req++; + + if (!(s->flags & SN_ERR_MASK)) + s->flags |= SN_ERR_PRXCOND; + if (!(s->flags & SN_FINST_MASK)) + s->flags |= SN_FINST_R; + return 0; +} + /* Retrieves the original destination address used by the client, and sets the * SN_FRT_ADDR_SET flag. */ diff -ru haproxy-1.4.10/src/proto_http.c haproxy-1.4.10-accept-proxy/src/proto_http.c --- haproxy-1.4.10/src/proto_http.c 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/src/proto_http.c 2010-12-07 22:16:56.764733152 +0100 @@ -3911,7 +3911,8 @@ if (s->rep->lr >= s->rep->data + s->rep->size) s->rep->lr -= s->req->size; - s->req->analysers |= s->fe->fe_req_ana; + s->req->analysers = s->fe->fe_req_ana; + s->req->analysers &= ~AN_REQ_DECODE_PROXY; s->rep->analysers = 0; http_silent_debug(__LINE__, s); @@ -7278,7 +7279,6 @@ http_init_txn(s); s->be = s->fe; - s->req->analysers = s->listener->analysers; s->logs.logwait = s->fe->to_log; s->srv = s->prev_srv = s->srv_conn = NULL; /* re-init store persistence */ diff -ru haproxy-1.4.10/src/session.c haproxy-1.4.10-accept-proxy/src/session.c --- haproxy-1.4.10/src/session.c 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/src/session.c 2010-12-07 21:51:33.605468023 +0100 @@ -1052,6 +1052,12 @@ while (ana_list && max_loops--) { /* Warning! ensure that analysers are always placed in ascending order! */ + if (ana_list & AN_REQ_DECODE_PROXY) { + if (!frontend_decode_proxy_request(s, s->req, AN_REQ_DECODE_PROXY)) + break; + UPDATE_ANALYSERS(s->req->analysers, ana_list, ana_back, AN_REQ_DECODE_PROXY); + } + if (ana_list & AN_REQ_INSPECT) { if (!tcp_inspect_request(s, s->req, AN_REQ_INSPECT)) break; diff -ru haproxy-1.4.10/src/standard.c haproxy-1.4.10-accept-proxy/src/standard.c --- haproxy-1.4.10/src/standard.c 2010-11-29 07:36:47.000000000 +0100 +++ haproxy-1.4.10-accept-proxy/src/standard.c 2010-12-07 21:59:17.394852621 +0100 @@ -535,6 +535,11 @@ return __strl2uic(s, len); } +unsigned int read_uint(const char **s, const char *end) +{ + return __read_uint(s, end); +} + /* This one is 7 times faster than strtol() on athlon with checks. * It returns the value of the number composed of all valid digits read, * and can process negative numbers too.
diff -ru stunnel-4.34/src/client.c stunnel-4.34-exceliance-aloha-sendproxy/src/client.c --- stunnel-4.34/src/client.c 2010-09-14 17:03:43.000000000 +0200 +++ stunnel-4.34-exceliance-aloha-sendproxy/src/client.c 2010-12-07 22:46:32.421248698 +0100 @@ -86,6 +86,8 @@ c->opt=opt; c->local_rfd.fd=rfd; c->local_wfd.fd=wfd; + if (c->opt->option.sendproxy) + c->sendproxy = 1; return c; } @@ -564,6 +566,73 @@ } } + if (c->sendproxy && !c->ssl_ptr) { + int cfd; + struct sockaddr_storage local_addr; + struct sockaddr_storage peer_addr; + u_char family = AF_UNSPEC; + + cfd = SSL_get_fd(c->ssl); + if (cfd != -1) { + size_t namelen; + + namelen = sizeof(local_addr); + if (!getsockname(cfd, (struct sockaddr *)&local_addr, &namelen)) { + namelen = sizeof(peer_addr); + if (!getpeername(cfd, (struct sockaddr *)&peer_addr, &namelen)) + family = peer_addr.ss_family; + } + } + + if (family == AF_INET) { + + if (BUFFSIZE >= 11) { + memcpy(c->ssl_buff, "PROXY TCP4 ", 11); + c->ssl_ptr += 11; + } + + if (inet_ntop(peer_addr.ss_family, &((struct sockaddr_in*)&peer_addr)->sin_addr, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr)) { + c->ssl_ptr += strlen(c->ssl_buff+c->ssl_ptr); + } + if (c->ssl_ptr != BUFFSIZE) { + c->ssl_buff[c->ssl_ptr] = ' '; + c->ssl_ptr++; + } + if (inet_ntop(local_addr.ss_family, &((struct sockaddr_in*)&local_addr)->sin_addr, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr)) { + c->ssl_ptr += strlen(c->ssl_buff+c->ssl_ptr); + } + c->ssl_ptr += snprintf(c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr, " %u %u\r\n", ntohs(((struct sockaddr_in*)&peer_addr)->sin_port), ntohs(((struct sockaddr_in*)&local_addr)->sin_port)); + } +#if defined(USE_IPv6) && !defined(USE_WIN32) + else if (family == AF_INET6) { + + if (BUFFSIZE >= 11) { + memcpy(c->ssl_buff, "PROXY TCP6 ", 11); + c->ssl_ptr += 11; + } + + if (inet_ntop(peer_addr.ss_family, &((struct sockaddr_in6*)&peer_addr)->sin6_addr, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr)) { + c->ssl_ptr += strlen(c->ssl_buff+c->ssl_ptr); + } + if (c->ssl_ptr != BUFFSIZE) { + c->ssl_buff[c->ssl_ptr] = ' '; + c->ssl_ptr++; + } + if (inet_ntop(local_addr.ss_family, &((struct sockaddr_in6*)&local_addr)->sin6_addr, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr)) { + c->ssl_ptr += strlen(c->ssl_buff+c->ssl_ptr); + } + c->ssl_ptr += snprintf(c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr, " %u %u\r\n", ntohs(((struct sockaddr_in6*)&peer_addr)->sin6_port), ntohs(((struct sockaddr_in6*)&local_addr)->sin6_port)); + } +#endif + else { + if (BUFFSIZE >= 15) { + memcpy(c->ssl_buff, "PROXY UNKNOWN\r\n ", 15); + c->ssl_ptr += 15; + } + } + c->sendproxy = 0; + } + /****************************** update *_wants_* based on new *_ptr */ /* this update is also required for SSL_pending() to be used */ read_wants_read= diff -ru stunnel-4.34/src/options.c stunnel-4.34-exceliance-aloha-sendproxy/src/options.c --- stunnel-4.34/src/options.c 2010-09-14 17:09:36.000000000 +0200 +++ stunnel-4.34-exceliance-aloha-sendproxy/src/options.c 2010-12-07 22:46:26.613204761 +0100 @@ -818,6 +818,29 @@ } #endif + /* sendproxy */ + switch(cmd) { + case CMD_INIT: + section->option.sendproxy=0; + break; + case CMD_EXEC: + if(strcasecmp(opt, "sendproxy")) + break; + if(!strcasecmp(arg, "yes")) + section->option.sendproxy=1; + else if(!strcasecmp(arg, "no")) + section->option.sendproxy=0; + else + return "argument should be either 'yes' or 'no'"; + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + s_log(LOG_NOTICE, "%-15s = yes|no append proxy prefix", + "sendproxy"); + break; + } + /* exec */ switch(cmd) { case CMD_INIT: diff -ru stunnel-4.34/src/prototypes.h stunnel-4.34-exceliance-aloha-sendproxy/src/prototypes.h --- stunnel-4.34/src/prototypes.h 2010-09-14 17:09:50.000000000 +0200 +++ stunnel-4.34-exceliance-aloha-sendproxy/src/prototypes.h 2010-12-07 22:47:39.633763055 +0100 @@ -176,6 +176,7 @@ unsigned int retry:1; /* loop remote+program */ unsigned int sessiond:1; unsigned int program:1; + unsigned int sendproxy:1; #ifndef USE_WIN32 unsigned int pty:1; unsigned int transparent:1; @@ -341,6 +342,7 @@ char sock_buff[BUFFSIZE]; /* socket read buffer */ char ssl_buff[BUFFSIZE]; /* SSL read buffer */ + int sendproxy; int sock_ptr, ssl_ptr; /* index of first unused byte in buffer */ FD *sock_rfd, *sock_wfd; /* read and write socket descriptors */ FD *ssl_rfd, *ssl_wfd; /* read and write SSL descriptors */