Hi Willy--
Here's my latest on extending Proxy Protocol V2.
I'm still testing this, but I would like to solicit any feedback that
you may have.
I believe I have incorporated all of your comments to date.
So far, I have implemented CN as a first sub-vector. I'm willing to
write a couple others, if you would like to suggest any.
Thanks,
--Dave
diff --git a/include/proto/connection.h b/include/proto/connection.h
index 8609f17..0db677e 100644
--- a/include/proto/connection.h
+++ b/include/proto/connection.h
@@ -41,7 +41,9 @@ int conn_fd_handler(int fd);
/* receive a PROXY protocol header over a connection */
int conn_recv_proxy(struct connection *conn, int flag);
-int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst);
+int make_proxy_line(char *buf, int buf_len, struct server *srv, struct
connection *remote);
+int make_proxy_line_v1(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst);
+int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct
connection *remote);
/* returns true is the transport layer is ready */
static inline int conn_xprt_ready(const struct connection *conn)
diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h
index 9d891d9..454edd5 100644
--- a/include/proto/ssl_sock.h
+++ b/include/proto/ssl_sock.h
@@ -40,6 +40,10 @@ int ssl_sock_prepare_srv_ctx(struct server *srv, struct
proxy *px);
void ssl_sock_free_all_ctx(struct bind_conf *bind_conf);
const char *ssl_sock_get_cipher_name(struct connection *conn);
const char *ssl_sock_get_proto_version(struct connection *conn);
+int ssl_sock_is_ssl(struct connection *conn);
+int ssl_sock_get_cert_used(struct connection *conn);
+char *ssl_sock_get_common_name(struct connection *conn);
+unsigned int ssl_sock_get_verify_result(struct connection *conn);
#endif /* _PROTO_SSL_SOCK_H */
diff --git a/include/types/connection.h b/include/types/connection.h
index 5341a86..953cb16 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -265,6 +265,87 @@ struct connection {
} addr; /* addresses of the remote side, client for producer and server
for consumer */
};
+/* proxy protocol v2 definitions */
+#define PP2_SIGNATURE_LEN 12
+#define PP2_HEADER_LEN 16
+#define PP2_VERSION 0x20
+#define PP2_CMD_LOCAL 0x00
+#define PP2_CMD_PROXY 0x01
+#define PP2_FAM_UNSPEC 0x00
+#define PP2_FAM_INET 0x10
+#define PP2_FAM_INET6 0x20
+#define PP2_FAM_UNIX 0x30
+#define PP2_TRANS_UNSPEC 0x00
+#define PP2_TRANS_STREAM 0x01
+#define PP2_TRANS_DGRAM 0x02
+
+#define PP2_ADDR_LEN_UNSPEC 0
+#define PP2_ADDR_LEN_INET 12
+#define PP2_ADDR_LEN_INET6 36
+#define PP2_ADDR_LEN_UNIX 216
+
+#define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
+#define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
+#define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
+#define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
+
+struct proxy_hdr_v2 {
+ uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ uint8_t cmd; /* protocol version and command */
+ uint8_t fam; /* protocol family and transport */
+ uint16_t len; /* number of following bytes part of the header */
+};
+
+union proxy_addr {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ uint32_t src_addr;
+ uint32_t dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv4_addr;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ uint8_t src_addr[16];
+ uint8_t dst_addr[16];
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv6_addr;
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unix_addr;
+};
+
+#define PP2_TYPE_SSL 0x20
+#define PP2_TYPE_SSL_CN 0x21
+#define PP2_TYPE_SSL_DN 0x22
+
+struct tlv {
+ uint16_t length;
+ uint8_t type;
+ uint8_t value[0];
+}__attribute__((packed));
+
+struct tlv_ssl {
+ struct tlv tlv;
+ uint32_t version;
+ uint32_t client;
+ uint32_t verify;
+ uint8_t sub_tlv[0];
+}__attribute__((packed));
+
+#define PP2_CLIENT_SSL 0x00000001
+#define PP2_CLIENT_CERT 0x00000002
+
+struct tlv_cn {
+ struct tlv tlv;
+ uint8_t cn[0];
+}__attribute__((packed));
+
+struct tlv_dn {
+ struct tlv tlv;
+ uint8_t dn[0];
+}__attribute__((packed));
+
#endif /* _TYPES_CONNECTION_H */
/*
diff --git a/include/types/server.h b/include/types/server.h
index 54ab813..8c4c784 100644
--- a/include/types/server.h
+++ b/include/types/server.h
@@ -57,6 +57,12 @@
#define SRV_SEND_PROXY 0x0800 /* this server talks the PROXY protocol */
#define SRV_NON_STICK 0x1000 /* never add connections allocated to this
server to a stick table */
+/* configured server options for send-proxy */
+#define SRV_PP_V2 0x0001 /* proxy protocol version 2 */
+#define SRV_PP_V2_SSL 0x0002 /* proxy protocol version 2 with SSL*/
+#define SRV_PP_V2_SSL_CN 0x0004 /* proxy protocol version 2 with SSL
and CN*/
+#define SRV_PP_V2_SSL_DN 0x0008 /* proxy protocol version 2 with SSL
and DN*/
+
/* function which act on servers need to return various errors */
#define SRV_STATUS_OK 0 /* everything is OK. */
#define SRV_STATUS_INTERNAL 1 /* other unrecoverable errors. */
@@ -106,6 +112,7 @@ struct server {
int rdr_len; /* the length of the
redirection prefix */
char *cookie; /* the id set in the cookie */
char *rdr_pfx; /* the redirection prefix */
+ int pp_opts; /* proxy protocol options */
struct proxy *proxy; /* the proxy this server
belongs to */
int served; /* # of active sessions
currently being served (ie not pending) */
diff --git a/src/connection.c b/src/connection.c
index 1483f18..13abc16 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -437,6 +437,23 @@ int conn_recv_proxy(struct connection *conn, int flag)
return 0;
}
+int make_proxy_line(char *buf, int buf_len, struct server *srv, struct
connection *remote)
+{
+ int ret = 0;
+
+ if (srv && (srv->pp_opts & SRV_PP_V2)) {
+ ret = make_proxy_line_v2(buf, buf_len, srv, remote);
+ }
+ else {
+ if (remote)
+ ret = make_proxy_line_v1(buf, buf_len,
&remote->addr.from, &remote->addr.to);
+ else
+ ret = make_proxy_line_v1(buf, buf_len, NULL, NULL);
+ }
+
+ return ret;
+}
+
/* Makes a PROXY protocol line from the two addresses. The output is sent to
* buffer <buf> for a maximum size of <buf_len> (including the trailing zero).
* It returns the number of bytes composing this line (including the trailing
@@ -444,7 +461,7 @@ int conn_recv_proxy(struct connection *conn, int flag)
* TCP6 and "UNKNOWN" formats. If any of <src> or <dst> is null, UNKNOWN is
* emitted as well.
*/
-int make_proxy_line(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst)
+int make_proxy_line_v1(char *buf, int buf_len, struct sockaddr_storage *src,
struct sockaddr_storage *dst)
{
int ret = 0;
@@ -516,3 +533,96 @@ int make_proxy_line(char *buf, int buf_len, struct
sockaddr_storage *src, struct
}
return ret;
}
+
+int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct
connection *remote)
+{
+ const char pp2_signature[12] = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A};
+ int ret = 0;
+ struct proxy_hdr_v2 *hdr_p = (struct proxy_hdr_v2 *)buf;
+ union proxy_addr *addr_p = (union proxy_addr *)(buf + PP2_HEADER_LEN);
+ struct sockaddr_storage null_addr = {0};
+ struct sockaddr_storage *src = &null_addr;
+ struct sockaddr_storage *dst = &null_addr;
+
+ if (buf_len < PP2_HEADER_LEN)
+ return 0;
+ memcpy(hdr_p->sig, pp2_signature, PP2_SIGNATURE_LEN);
+
+ if (remote) {
+ src = &remote->addr.from;
+ dst = &remote->addr.to;
+ }
+ if (src && dst && src->ss_family == dst->ss_family && src->ss_family ==
AF_INET) {
+ if (buf_len < PP2_HDR_LEN_INET)
+ return 0;
+ hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY;
+ hdr_p->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
+ addr_p->ipv4_addr.src_addr = ((struct sockaddr_in
*)src)->sin_addr.s_addr;
+ addr_p->ipv4_addr.dst_addr = ((struct sockaddr_in
*)dst)->sin_addr.s_addr;
+ addr_p->ipv4_addr.src_port = ((struct sockaddr_in
*)src)->sin_port;
+ addr_p->ipv4_addr.dst_port = ((struct sockaddr_in
*)dst)->sin_port;
+ ret = PP2_HDR_LEN_INET;
+ }
+ else if (src && dst && src->ss_family == dst->ss_family &&
src->ss_family == AF_INET6) {
+ if (buf_len < PP2_HDR_LEN_INET6)
+ return 0;
+ hdr_p->cmd = PP2_VERSION | PP2_CMD_PROXY;
+ hdr_p->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
+ memcpy(addr_p->ipv6_addr.src_addr, &((struct sockaddr_in6
*)src)->sin6_addr, 16);
+ memcpy(addr_p->ipv6_addr.dst_addr, &((struct sockaddr_in6
*)dst)->sin6_addr, 16);
+ addr_p->ipv6_addr.src_port = ((struct sockaddr_in6
*)src)->sin6_port;
+ addr_p->ipv6_addr.dst_port = ((struct sockaddr_in6
*)dst)->sin6_port;
+ ret = PP2_HDR_LEN_INET6;
+ }
+ else {
+ if (buf_len < PP2_HDR_LEN_UNSPEC)
+ return 0;
+ hdr_p->cmd = PP2_VERSION | PP2_CMD_LOCAL;
+ hdr_p->fam = PP2_FAM_UNSPEC | PP2_TRANS_UNSPEC;
+ ret = PP2_HDR_LEN_UNSPEC;
+ }
+
+#ifdef USE_OPENSSL
+ if (srv->pp_opts & SRV_PP_V2_SSL) {
+ int ssl_tlv_len = 0;
+ if ((buf_len - ret) < sizeof(struct tlv_ssl))
+ return 0;
+ struct tlv_ssl *tlv = (struct tlv_ssl *)&buf[ret];
+ memset(tlv, 0, sizeof(struct tlv_ssl));
+ ssl_tlv_len += sizeof(struct tlv_ssl);
+ tlv->tlv.type = PP2_TYPE_SSL;
+ if (ssl_sock_is_ssl(remote)) {
+ tlv->client |= PP2_CLIENT_SSL;
+ if (ssl_sock_get_cert_used(remote)) {
+ tlv->client |= PP2_CLIENT_CERT;
+ tlv->verify =
htonl(ssl_sock_get_verify_result(remote));
+ }
+ if (srv->pp_opts & SRV_PP_V2_SSL_CN) {
+ char *cn = ssl_sock_get_common_name(remote);
+ if (cn) {
+ int cn_len = strlen(cn);
+ if ((buf_len - ret - ssl_tlv_len) <
cn_len + sizeof(struct tlv_cn))
+ return 0;
+ struct tlv_cn *tlv_cn = (struct tlv_cn
*)&buf[ret+ssl_tlv_len];
+ memset(tlv_cn, 0, sizeof(struct
tlv_cn));
+ tlv_cn->tlv.type = PP2_TYPE_SSL_CN;
+ tlv_cn->tlv.length = htons(cn_len +
sizeof(tlv_cn));
+ memcpy(tlv_cn->cn, cn, cn_len);
+ ssl_tlv_len += cn_len + sizeof(tlv_cn);
+ }
+ }
+
+ if (srv->pp_opts & SRV_PP_V2_SSL_DN) {
+
+ }
+
+ }
+ tlv->tlv.length = htons((uint16_t)ssl_tlv_len);
+ ret += ssl_tlv_len;
+ }
+#endif
+
+ hdr_p->len = htons((uint16_t)(ret - PP2_HEADER_LEN));
+
+ return ret;
+}
diff --git a/src/server.c b/src/server.c
index 5ac5e37..cd651a0 100644
--- a/src/server.c
+++ b/src/server.c
@@ -615,6 +615,11 @@ int parse_server(const char *file, int linenum, char
**args, struct proxy *curpr
newsrv->state |= SRV_SEND_PROXY;
cur_arg ++;
}
+ else if (!defsrv && !strcmp(args[cur_arg],
"send-proxy-v2")) {
+ newsrv->state |= SRV_SEND_PROXY;
+ newsrv->pp_opts |= SRV_PP_V2;
+ cur_arg ++;
+ }
else if (!defsrv && !strcmp(args[cur_arg],
"check-send-proxy")) {
newsrv->check.send_proxy = 1;
cur_arg ++;
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 525c7b5..49e3c06 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -1896,6 +1896,80 @@ ssl_sock_get_dn_oneline(X509_NAME *a, struct chunk *out)
return 1;
}
+/* boolean, returns true if connection is over SSL */
+int ssl_sock_is_ssl(struct connection *conn)
+{
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return 0;
+ else
+ return 1;
+}
+
+/* returns common name, NULL terminated, from client certificate, or NULL if
none */
+char *ssl_sock_get_common_name(struct connection *conn)
+{
+ X509 *crt = NULL;
+ X509_NAME *name;
+ struct chunk *trash;
+ const char find_cn[] = "CN";
+ const struct chunk find_cn_chunk = {
+ .str = (char *)&find_cn,
+ .len = sizeof(find_cn)-1
+ };
+ char *result = NULL;
+
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return NULL;
+
+ /* SSL_get_peer_certificate, it increase X509 * ref count */
+ crt = SSL_get_peer_certificate(conn->xprt_ctx);
+ if (!crt)
+ goto out;
+
+ name = X509_get_subject_name(crt);
+ if (!name)
+ goto out;
+
+ trash = get_trash_chunk();
+ if (ssl_sock_get_dn_entry(name, &find_cn_chunk, 1, trash) <= 0)
+ goto out;
+ trash->str[trash->len] = '\0';
+ result = trash->str;
+
+ out:
+ if (crt)
+ X509_free(crt);
+
+ return result;
+}
+
+/* returns 1 if client passed a certificate, 0 if not */
+int ssl_sock_get_cert_used(struct connection *conn)
+{
+ X509 *crt = NULL;
+
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return 0;
+
+ /* SSL_get_peer_certificate, it increase X509 * ref count */
+ crt = SSL_get_peer_certificate(conn->xprt_ctx);
+ if (crt) {
+ X509_free(crt);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* returns result from SSL verify */
+unsigned int ssl_sock_get_verify_result(struct connection *conn)
+{
+ if (!conn || conn->xprt != &ssl_sock || !conn->xprt_ctx)
+ return (unsigned int)X509_V_ERR_APPLICATION_VERIFICATION;
+
+ return (unsigned int)SSL_get_verify_result(conn->xprt_ctx);
+}
+
/***** Below are some sample fetching functions for ACL/patterns *****/
@@ -3605,6 +3679,34 @@ static int srv_parse_no_tls_tickets(char **args, int
*cur_arg, struct proxy *px,
newsrv->ssl_ctx.options |= SRV_SSL_O_NO_TLS_TICKETS;
return 0;
}
+/* parse the "send-proxy-v2-ssl" server keyword */
+static int srv_parse_send_proxy_ssl(char **args, int *cur_arg, struct proxy
*px, struct server *newsrv, char **err)
+{
+ newsrv->state |= SRV_SEND_PROXY;
+ newsrv->pp_opts |= SRV_PP_V2;
+ newsrv->pp_opts |= SRV_PP_V2_SSL;
+ return 0;
+}
+
+/* parse the "send-proxy-v2-ssl-cn" server keyword */
+static int srv_parse_send_proxy_cn(char **args, int *cur_arg, struct proxy
*px, struct server *newsrv, char **err)
+{
+ newsrv->state |= SRV_SEND_PROXY;
+ newsrv->pp_opts |= SRV_PP_V2;
+ newsrv->pp_opts |= SRV_PP_V2_SSL;
+ newsrv->pp_opts |= SRV_PP_V2_SSL_CN;
+ return 0;
+}
+
+/* parse the "send-proxy-v2-ssl-dn" server keyword */
+static int srv_parse_send_proxy_dn(char **args, int *cur_arg, struct proxy
*px, struct server *newsrv, char **err)
+{
+ newsrv->state |= SRV_SEND_PROXY;
+ newsrv->pp_opts |= SRV_PP_V2;
+ newsrv->pp_opts |= SRV_PP_V2_SSL;
+ newsrv->pp_opts |= SRV_PP_V2_SSL_DN;
+ return 0;
+}
/* parse the "ssl" server keyword */
static int srv_parse_ssl(char **args, int *cur_arg, struct proxy *px, struct
server *newsrv, char **err)
@@ -3784,6 +3886,9 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
{ "no-tlsv11", srv_parse_no_tlsv11, 0, 0 }, /* disable
TLSv11 */
{ "no-tlsv12", srv_parse_no_tlsv12, 0, 0 }, /* disable
TLSv12 */
{ "no-tls-tickets", srv_parse_no_tls_tickets, 0, 0 }, /* disable
session resumption tickets */
+ { "send-proxy-v2-ssl", srv_parse_send_proxy_ssl, 0, 0 }, /* send
PROXY protocol header v2 with SSL info */
+ { "send-proxy-v2-ssl-cn", srv_parse_send_proxy_cn, 0, 0 }, /* send
PROXY protocol header v2 with CN */
+ { "send-proxy-v2-ssl-dn", srv_parse_send_proxy_dn, 0, 0 }, /* send
PROXY protocol header v2 with DN */
{ "ssl", srv_parse_ssl, 0, 0 }, /* enable
SSL processing */
{ "verify", srv_parse_verify, 1, 0 }, /* set SSL
verify method */
{ "verifyhost", srv_parse_verifyhost, 1, 0 }, /* require
that SSL cert verifies for hostname */
diff --git a/src/stream_interface.c b/src/stream_interface.c
index f23a9b0..67a5234 100644
--- a/src/stream_interface.c
+++ b/src/stream_interface.c
@@ -422,11 +422,7 @@ int conn_si_send_proxy(struct connection *conn, unsigned
int flag)
if (conn->data == &si_conn_cb) {
struct stream_interface *si = conn->owner;
struct connection *remote =
objt_conn(si->ob->prod->end);
-
- if (remote)
- ret = make_proxy_line(trash.str, trash.size,
&remote->addr.from, &remote->addr.to);
- else
- ret = make_proxy_line(trash.str, trash.size,
NULL, NULL);
+ ret = make_proxy_line(trash.str, trash.size,
objt_server(conn->target), remote);
}
else {
/* The target server expects a LOCAL line to be sent
first. Retrieving
@@ -440,7 +436,7 @@ int conn_si_send_proxy(struct connection *conn, unsigned
int flag)
if (!(conn->flags & CO_FL_ADDR_TO_SET))
goto out_wait;
- ret = make_proxy_line(trash.str, trash.size,
&conn->addr.from, &conn->addr.to);
+ ret = make_proxy_line(trash.str, trash.size,
objt_server(conn->target), conn);
}
if (!ret)