Skip to content

Commit b8fec4c

Browse files
authored
Merge pull request #173 from Castaglia/proxy-protocolv2-issue151
Issue #151: Implement support for the PROXY V2 protocol.
2 parents 3e61f26 + 6045b2b commit b8fec4c

File tree

8 files changed

+532
-35
lines changed

8 files changed

+532
-35
lines changed

include/proxy/conn.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* ProFTPD - mod_proxy conn API
3-
* Copyright (c) 2012-2016 TJ Saunders
3+
* Copyright (c) 2012-2020 TJ Saunders
44
*
55
* This program is free software; you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -45,7 +45,8 @@ const char *proxy_conn_get_uri(const struct proxy_conn *pconn);
4545
const char *proxy_conn_get_username(const struct proxy_conn *pconn);
4646
const char *proxy_conn_get_password(const struct proxy_conn *pconn);
4747
int proxy_conn_get_tls(const struct proxy_conn *pconn);
48-
int proxy_conn_send_proxy(pool *p, conn_t *conn);
48+
int proxy_conn_send_proxy_v1(pool *p, conn_t *conn);
49+
int proxy_conn_send_proxy_v2(pool *p, conn_t *conn);
4950
void proxy_conn_free(const struct proxy_conn *pconn);
5051

5152
#endif /* MOD_PROXY_CONN_H */

lib/proxy/conn.c

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424

2525
#include "mod_proxy.h"
2626

27+
#ifdef HAVE_SYS_UIO_H
28+
# include <sys/uio.h>
29+
#endif /* HAVE_SYS_UIO_H */
30+
2731
#include "proxy/conn.h"
2832
#include "proxy/netio.h"
2933
#include "proxy/inet.h"
@@ -60,6 +64,16 @@ static const char *supported_protocols[] = {
6064
NULL
6165
};
6266

67+
/* PROXY protocol V2 */
68+
#define PROXY_PROTOCOL_V2_SIGLEN 12
69+
#define PROXY_PROTOCOL_V2_HDRLEN 16
70+
#define PROXY_PROTOCOL_V2_TRANSPORT_STREAM 0x01
71+
#define PROXY_PROTOCOL_V2_FAMILY_INET 0x10
72+
#define PROXY_PROTOCOL_V2_FAMILY_INET6 0x20
73+
#define PROXY_PROTOCOL_V2_ADDRLEN_INET (4 + 4 + 2 + 2)
74+
#define PROXY_PROTOCOL_V2_ADDRLEN_INET6 (16 + 16 + 2 + 2)
75+
static uint8_t proxy_protocol_v2_sig[PROXY_PROTOCOL_V2_SIGLEN] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
76+
6377
static const char *trace_channel = "proxy.conn";
6478

6579
static int supported_protocol(const char *proto) {
@@ -457,7 +471,8 @@ conn_t *proxy_conn_get_server_conn(pool *p, struct proxy_session *proxy_sess,
457471
pr_netio_stream_t *nstrm;
458472
int connected = FALSE, nstrm_mode = PR_NETIO_IO_RD;
459473

460-
if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL) {
474+
if ((proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) ||
475+
(proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2)) {
461476
/* Rather than waiting for the stream to be readable (because the
462477
* other end sent us something), wait for the stream to be writable
463478
* so that we can send something to the other end).
@@ -595,7 +610,7 @@ const char *proxy_conn_get_uri(const struct proxy_conn *pconn) {
595610
return pconn->pconn_uri;
596611
}
597612

598-
int proxy_conn_send_proxy(pool *p, conn_t *conn) {
613+
int proxy_conn_send_proxy_v1(pool *p, conn_t *conn) {
599614
int res, src_port, dst_port;
600615
const char *proto, *src_ipstr, *dst_ipstr;
601616
pool *sub_pool = NULL;
@@ -664,7 +679,7 @@ int proxy_conn_send_proxy(pool *p, conn_t *conn) {
664679
}
665680

666681
pr_trace_msg(trace_channel, 9,
667-
"sending proxy protocol message: 'PROXY %s %s %s %d %d' to backend",
682+
"sending PROXY protocol V1 message: 'PROXY %s %s %s %d %d' to backend",
668683
proto, src_ipstr, dst_ipstr, src_port, dst_port);
669684

670685
res = proxy_netio_printf(conn->outstrm, "PROXY %s %s %s %d %d\r\n",
@@ -676,3 +691,151 @@ int proxy_conn_send_proxy(pool *p, conn_t *conn) {
676691

677692
return res;
678693
}
694+
695+
static int writev_conn(conn_t *conn, const struct iovec *iov, int iov_count) {
696+
int res, xerrno;
697+
698+
if (pr_netio_poll(conn->outstrm) < 0) {
699+
return -1;
700+
}
701+
702+
res = writev(conn->wfd, iov, iov_count);
703+
xerrno = errno;
704+
705+
while (res <= 0) {
706+
if (res < 0) {
707+
if (xerrno == EINTR) {
708+
pr_signals_handle();
709+
710+
if (pr_netio_poll(conn->outstrm) < 0) {
711+
return -1;
712+
}
713+
714+
res = writev(conn->wfd, iov, iov_count);
715+
xerrno = errno;
716+
717+
continue;
718+
}
719+
720+
pr_trace_msg(trace_channel, 16,
721+
"error writing to client (fd %d): %s", conn->wfd, strerror(xerrno));
722+
errno = errno;
723+
return -1;
724+
}
725+
}
726+
727+
session.total_raw_out += res;
728+
return res;
729+
}
730+
731+
int proxy_conn_send_proxy_v2(pool *p, conn_t *conn) {
732+
int res, xerrno;
733+
uint8_t ver_cmd, trans_fam, src_ipv6[16], dst_ipv6[16];
734+
uint16_t v2_len, src_port, dst_port;
735+
uint32_t src_ipv4, dst_ipv4;
736+
struct iovec v2_hdr[8];
737+
pool *sub_pool = NULL;
738+
char *proto;
739+
const pr_netaddr_t *src_addr = NULL, *dst_addr = NULL;
740+
741+
if (p == NULL ||
742+
conn == NULL) {
743+
errno = EINVAL;
744+
return -1;
745+
}
746+
747+
v2_hdr[0].iov_base = (void *) proxy_protocol_v2_sig;
748+
v2_hdr[0].iov_len = PROXY_PROTOCOL_V2_SIGLEN;
749+
750+
/* PROXY protocol v2 + PROXY command */
751+
ver_cmd = (0x20|0x01);
752+
v2_hdr[1].iov_base = (void *) &ver_cmd;
753+
v2_hdr[1].iov_len = sizeof(ver_cmd);
754+
755+
src_addr = session.c->remote_addr;
756+
dst_addr = session.c->local_addr;
757+
758+
if (pr_netaddr_get_family(src_addr) == AF_INET &&
759+
pr_netaddr_get_family(dst_addr) == AF_INET) {
760+
struct sockaddr_in *saddr;
761+
762+
proto = "TCP/IPv4";
763+
trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET);
764+
v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET;
765+
766+
saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(src_addr);
767+
src_ipv4 = saddr->sin_addr.s_addr;
768+
v2_hdr[4].iov_base = (void *) &src_ipv4;
769+
v2_hdr[4].iov_len = sizeof(src_ipv4);
770+
771+
saddr = (struct sockaddr_in *) pr_netaddr_get_sockaddr(dst_addr);
772+
dst_ipv4 = saddr->sin_addr.s_addr;
773+
v2_hdr[5].iov_base = (void *) &dst_ipv4;
774+
v2_hdr[5].iov_len = sizeof(dst_ipv4);
775+
776+
/* Quell compiler warnings about unused variables. */
777+
(void) src_ipv6;
778+
(void) dst_ipv6;
779+
780+
} else {
781+
struct sockaddr_in6 *saddr;
782+
783+
proto = "TCP/IPv6";
784+
trans_fam = (PROXY_PROTOCOL_V2_TRANSPORT_STREAM|PROXY_PROTOCOL_V2_FAMILY_INET6);
785+
v2_len = PROXY_PROTOCOL_V2_ADDRLEN_INET6;
786+
787+
sub_pool = make_sub_pool(p);
788+
789+
if (pr_netaddr_get_family(src_addr) == AF_INET) {
790+
src_addr = pr_netaddr_v4tov6(sub_pool, src_addr);
791+
}
792+
793+
saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(src_addr);
794+
memcpy(&src_ipv6, &(saddr->sin6_addr), sizeof(src_ipv6));
795+
v2_hdr[4].iov_base = (void *) &src_ipv6;
796+
v2_hdr[4].iov_len = sizeof(src_ipv6);
797+
798+
if (pr_netaddr_get_family(dst_addr) == AF_INET) {
799+
dst_addr = pr_netaddr_v4tov6(sub_pool, dst_addr);
800+
}
801+
802+
saddr = (struct sockaddr_in6 *) pr_netaddr_get_sockaddr(dst_addr);
803+
memcpy(&dst_ipv6, &(saddr->sin6_addr), sizeof(dst_ipv6));
804+
v2_hdr[5].iov_base = (void *) &dst_ipv6;
805+
v2_hdr[5].iov_len = sizeof(dst_ipv6);
806+
807+
/* Quell compiler warnings about unused variables. */
808+
(void) src_ipv4;
809+
(void) dst_ipv4;
810+
}
811+
812+
v2_hdr[2].iov_base = (void *) &trans_fam;
813+
v2_hdr[2].iov_len = sizeof(trans_fam);
814+
815+
v2_len = htons(v2_len);
816+
v2_hdr[3].iov_base = (void *) &v2_len;
817+
v2_hdr[3].iov_len = sizeof(v2_len);
818+
819+
src_port = htons(session.c->remote_port);
820+
v2_hdr[6].iov_base = (void *) &src_port;
821+
v2_hdr[6].iov_len = sizeof(src_port);
822+
823+
dst_port = htons(session.c->local_port);
824+
v2_hdr[7].iov_base = (void *) &dst_port;
825+
v2_hdr[7].iov_len = sizeof(dst_port);
826+
827+
pr_trace_msg(trace_channel, 9,
828+
"sending PROXY protocol V2 message for %s %s#%u %s#%u to backend",
829+
proto, pr_netaddr_get_ipstr(src_addr), (unsigned int) ntohs(src_port),
830+
pr_netaddr_get_ipstr(dst_addr), (unsigned int) ntohs(dst_port));
831+
832+
res = writev_conn(conn, v2_hdr, 8);
833+
xerrno = errno;
834+
835+
if (sub_pool != NULL) {
836+
destroy_pool(sub_pool);
837+
}
838+
839+
errno = xerrno;
840+
return res;
841+
}

lib/proxy/reverse.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -765,15 +765,29 @@ static int reverse_try_connect(pool *p, struct proxy_session *proxy_sess,
765765
return -1;
766766
}
767767

768-
if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL) {
768+
if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V1) {
769769
pr_trace_msg(trace_channel, 17,
770-
"sending PROXY protocol message to %s#%u",
770+
"sending PROXY V1 protocol message to %s#%u",
771771
pr_netaddr_get_ipstr(server_conn->remote_addr),
772772
ntohs(pr_netaddr_get_port(server_conn->remote_addr)));
773773

774-
if (proxy_conn_send_proxy(p, server_conn) < 0) {
774+
if (proxy_conn_send_proxy_v1(p, server_conn) < 0) {
775775
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
776-
"error sending PROXY message to %s#%u: %s",
776+
"error sending PROXY V1 message to %s#%u: %s",
777+
pr_netaddr_get_ipstr(server_conn->remote_addr),
778+
ntohs(pr_netaddr_get_port(server_conn->remote_addr)),
779+
strerror(errno));
780+
}
781+
782+
} else if (proxy_opts & PROXY_OPT_USE_PROXY_PROTOCOL_V2) {
783+
pr_trace_msg(trace_channel, 17,
784+
"sending PROXY V2 protocol message to %s#%u",
785+
pr_netaddr_get_ipstr(server_conn->remote_addr),
786+
ntohs(pr_netaddr_get_port(server_conn->remote_addr)));
787+
788+
if (proxy_conn_send_proxy_v2(p, server_conn) < 0) {
789+
(void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION,
790+
"error sending PROXY V2 message to %s#%u: %s",
777791
pr_netaddr_get_ipstr(server_conn->remote_addr),
778792
ntohs(pr_netaddr_get_port(server_conn->remote_addr)),
779793
strerror(errno));

mod_proxy.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,8 +763,12 @@ MODRET set_proxyoptions(cmd_rec *cmd) {
763763
c = add_config_param(cmd->argv[0], 1, NULL);
764764

765765
for (i = 1; i < cmd->argc; i++) {
766-
if (strcmp(cmd->argv[i], "UseProxyProtocol") == 0) {
767-
opts |= PROXY_OPT_USE_PROXY_PROTOCOL;
766+
if (strcmp(cmd->argv[i], "UseProxyProtocol") == 0 ||
767+
strcmp(cmd->argv[i], "UseProxyProtocolV1") == 0) {
768+
opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V1;
769+
770+
} else if (strcmp(cmd->argv[i], "UseProxyProtocolV2") == 0) {
771+
opts |= PROXY_OPT_USE_PROXY_PROTOCOL_V2;
768772

769773
} else if (strcmp(cmd->argv[i], "ShowFeatures") == 0) {
770774
opts |= PROXY_OPT_SHOW_FEATURES;

mod_proxy.h.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@
6666
#endif
6767

6868
/* mod_proxy option flags */
69-
#define PROXY_OPT_USE_PROXY_PROTOCOL 0x0001
69+
#define PROXY_OPT_USE_PROXY_PROTOCOL_V1 0x0001
7070
#define PROXY_OPT_SHOW_FEATURES 0x0002
7171
#define PROXY_OPT_USE_REVERSE_PROXY_AUTH 0x0004
7272
#define PROXY_OPT_USE_DIRECT_DATA_TRANSFERS 0x0008
7373
#define PROXY_OPT_IGNORE_CONFIG_PERMS 0x0010
74+
#define PROXY_OPT_USE_PROXY_PROTOCOL_V2 0x0020
7475

7576
/* mod_proxy datastores */
7677
#define PROXY_DATASTORE_SQLITE 1

mod_proxy.html

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ <h3><a name="ProxyOptions">ProxyOptions</a></h3>
455455
The <code>ProxyOptions</code> directive is used to configure various optional
456456
behavior of <code>mod_proxy</code>. For example:
457457
<pre>
458-
ProxyOptions UseProxyProtocol
458+
ProxyOptions UseProxyProtocolV1
459459
</pre>
460460

461461
<p>
@@ -488,10 +488,10 @@ <h3><a name="ProxyOptions">ProxyOptions</a></h3>
488488
</li>
489489

490490
<p>
491-
<li><code>UseProxyProtocol</code><br>
491+
<li><code>UseProxyProtocolV1</code><br>
492492
<p>
493493
When <code>mod_proxy</code> connects to the backend/destination server,
494-
use the <a href="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt"><code>PROXY</code></a> protocol, sending the human-readable <code>PROXY</code>
494+
use the <a href="http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt"><code>PROXY</code></a> V1 protocol, sending the human-readable <code>PROXY</code>
495495
command to the destination server. This allows backend servers to implement
496496
access controls/logging, based on the IP address of the connecting client.
497497
The <a href="https://github.com/Castaglia/proftpd-mod_proxy_protocol"><code>mod_proxy_protocol</code></a>
@@ -501,11 +501,30 @@ <h3><a name="ProxyOptions">ProxyOptions</a></h3>
501501

502502
<p>
503503
<b>Note</b>: do <b>not</b> use this option unless the backend server
504-
<em>does</em> support the <code>PROXY</code> protocol. Otherwise, the
504+
<em>does</em> support the <code>PROXY</code> V1 protocol. Otherwise, the
505505
<code>PROXY</code> protocol message will only confuse the backend server,
506506
possibly leading to connection/login failures.
507507
</li>
508508

509+
<p>
510+
<li><code>UseProxyProtocolV2</code><br>
511+
<p>
512+
When <code>mod_proxy</code> connects to the backend/destination server,
513+
use the <a href="http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt"><code>PROXY</code></a> V2 protocol, sending a binary-encoded "PROXY" command
514+
to the destination server. This allows backend servers to implement
515+
access controls/logging, based on the IP address of the connecting client.
516+
The <a href="https://github.com/Castaglia/proftpd-mod_proxy_protocol"><code>mod_proxy_protocol</code></a>
517+
ProFTPD module can be used to handle the "PROXY" command on the receiving
518+
side, <i>i.e.</i> when using <code>proftpd</code> as the
519+
backend/destination server.
520+
521+
<p>
522+
<b>Note</b>: do <b>not</b> use this option unless the backend server
523+
<em>does</em> support the <code>PROXY</code> V2 protocol. Otherwise, the
524+
"PROXY" protocol message will only confuse the backend server, possibly
525+
leading to connection/login failures.
526+
</li>
527+
509528
<p>
510529
<li><code>UseReverseProxyAuth</code><br>
511530
<p>

0 commit comments

Comments
 (0)