MINOR: proto-tcp: Add support for TCP MD5 signature for listeners and servers

This patch adds the support for the RFC2385 (Protection of BGP Sessions via
the + TCP MD5 Signature Option) for the listeners and the servers. The
feature is only available on Linux. Keywords are not exposed otherwise.

By setting "tcp-md5sig <password>" option on a bind line, TCP segments of
all connections instantiated from the listening socket will be signed with a
16-byte MD5 digest. The same option can be set on a server line to protect
outgoing connections to the corresponding server.

The primary use case for this option is to allow BGP to protect itself
against the introduction of spoofed TCP segments into the connection
stream. But it can be useful for any very long-lived TCP connections.

A reg-test was added and it will be executed only on linux. All other
targets are excluded.
This commit is contained in:
Christopher Faulet 2025-07-03 15:16:47 +02:00
parent 6f6c6fa4cb
commit 5232df57ab
9 changed files with 186 additions and 0 deletions

View File

@ -17079,6 +17079,16 @@ strict-sni
disabled on a "bind" line using "no-strict-sni". See the "crt" option for
more information. See "add ssl crt-list" command in the management guide.
tcp-md5sig <password>
Enables the TCP MD5 signature (RFC 2385 Protection of BGP Sessions via the
TCP MD5 Signature Option) for all incoming connections instantiated from this
listening socket. This option is only available on Linux. When enabled,
<password> string is used to sign every TCP segments with a 16-byte MD5
digest. This will protect the TCP connection against spoofing. The primary
use case for this option is to allow BGP to protect itself against the
introduction of spoofed TCP segments into the connection stream. But it can
be useful for any very long-lived TCP connections.
tcp-ut <delay>
Sets the TCP User Timeout for all incoming connections instantiated from this
listening socket. This option is available on Linux since version 2.6.37. It
@ -18766,6 +18776,18 @@ socks4 <addr>:<port>
server. Using this option won't force the health check to go via socks4 by
default. You will have to use the keyword "check-via-socks4" to enable it.
tcp-md5sig <password>
May be used in the following contexts: tcp, http, log, peers, ring
Enables the TCP MD5 signature (RFC 2385 Protection of BGP Sessions via the
TCP MD5 Signature Option) for all outgoing connections to this server. This
option is only available on Linux. When enabled, <password> string is used to
sign every TCP segments with a 16-byte MD5 digest. This will protect the TCP
connection against spoofing. The primary use case for this option is to allow
BGP to protect itself against the introduction of spoofed TCP segments into
the connection stream. But it can be useful for any very long-lived TCP
connections.
tcp-ut <delay>
May be used in the following contexts: tcp, http, log, peers, ring

View File

@ -193,6 +193,7 @@ struct bind_conf {
unsigned int analysers; /* bitmap of required protocol analysers */
int maxseg; /* for TCP, advertised MSS */
int tcp_ut; /* for TCP, user timeout */
char *tcp_md5sig; /* TCP MD5 signature password (RFC2385) */
int idle_ping; /* MUX idle-ping interval in ms */
int maxaccept; /* if set, max number of connections accepted at once (-1 when disabled) */
unsigned int backlog; /* if set, listen backlog */

View File

@ -431,6 +431,7 @@ struct server {
int puid; /* proxy-unique server ID, used for SNMP, and "first" LB algo */
int tcp_ut; /* for TCP, user timeout */
char *tcp_md5sig; /* TCP MD5 signature password (RFC2385) */
int do_check; /* temporary variable used during parsing to denote if health checks must be enabled */
int do_agent; /* temporary variable used during parsing to denote if an auxiliary agent check must be enabled */

View File

@ -0,0 +1,60 @@
varnishtest "Test the support for tcp-md5sig option (linux only)"
feature ignore_unknown_macro
feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(3.3-dev1)'"
#EXCLUDE_TARGETS=solaris,freebsd,freebsd-glibc,dragonfly,openbsd,netbsd,cygwin,haiku,aix51,aix52,aix72-gcc,osx,generic,custom
haproxy h1 -conf {
defaults
mode http
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
timeout connect "${HAPROXY_TEST_TIMEOUT-100ms}"
retries 0
log global
listen internal
bind "fd@${md5_int}" tcp-md5sig mypass
bind "fd@${nomd5_int}"
http-request return status 200
listen fe
bind "fd@${fe}"
use-server s1 if { path /s1 }
use-server s2 if { path /s2 }
use-server s3 if { path /s3 }
use-server s4 if { path /s4 }
server s1 ${h1_md5_int_addr}:${h1_md5_int_port} tcp-md5sig mypass
server s2 ${h1_md5_int_addr}:${h1_md5_int_port} tcp-md5sig badpass
server s3 ${h1_nomd5_int_addr}:${h1_nomd5_int_port}
server s4 ${h1_nomd5_int_addr}:${h1_nomd5_int_port} tcp-md5sig mypass
} -start
client c1 -connect ${h1_fe_sock} {
txreq -req "GET" -url "/s1"
rxresp
expect resp.status == 200
} -run
client c2 -connect ${h1_fe_sock} {
txreq -req "GET" -url "/s2"
rxresp
expect resp.status == 503
} -run
client c3 -connect ${h1_fe_sock} {
txreq -req "GET" -url "/s3"
rxresp
expect resp.status == 200
} -run
client c4 -connect ${h1_fe_sock} {
txreq -req "GET" -url "/s4"
rxresp
expect resp.status == 503
} -run

View File

@ -100,6 +100,31 @@ static int bind_parse_mss(char **args, int cur_arg, struct proxy *px, struct bin
}
#endif
#if defined(__linux__) && defined(TCP_MD5SIG)
/* parse the "tcp-md5sig" bind keyword */
static int bind_parse_tcp_md5sig(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
{
if (!*args[cur_arg + 1]) {
memprintf(err, "'%s' : missing TCP MD5 signature password", args[cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
ha_free(&conf->tcp_md5sig);
if (strlen(args[cur_arg + 1]) > TCP_MD5SIG_MAXKEYLEN) {
memprintf(err, "'%s' : password too long (at most %d characters expected)",
args[cur_arg], TCP_MD5SIG_MAXKEYLEN);
return ERR_ALERT | ERR_ABORT;
}
conf->tcp_md5sig = strdup(args[cur_arg + 1]);
if (!conf->tcp_md5sig) {
memprintf(err, "'%s %s' : out of memory", args[cur_arg], args[cur_arg + 1]);
return ERR_ALERT | ERR_FATAL;
}
return 0;
}
#endif
#ifdef TCP_USER_TIMEOUT
/* parse the "tcp-ut" bind keyword */
static int bind_parse_tcp_ut(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
@ -178,6 +203,32 @@ static int bind_parse_namespace(char **args, int cur_arg, struct proxy *px, stru
}
#endif
#if defined(__linux__) && defined(TCP_MD5SIG)
/* parse the "tcp-md5sig" server keyword */
static int srv_parse_tcp_md5sig(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
{
if (!*args[*cur_arg + 1]) {
memprintf(err, "'%s' : missing TCP MD5 signature password", args[*cur_arg]);
return ERR_ALERT | ERR_FATAL;
}
if (newsrv->addr.ss_family == AF_INET || newsrv->addr.ss_family == AF_INET6) {
ha_free(&newsrv->tcp_md5sig);
if (strlen(args[*cur_arg + 1]) > TCP_MD5SIG_MAXKEYLEN) {
memprintf(err, "'%s' : password too long (at most %d characters expected)",
args[*cur_arg], TCP_MD5SIG_MAXKEYLEN);
return ERR_ALERT | ERR_ABORT;
}
newsrv->tcp_md5sig = strdup(args[*cur_arg + 1]);
if (!newsrv->tcp_md5sig) {
memprintf(err, "'%s %s' : out of memory", args[*cur_arg], args[*cur_arg + 1]);
return ERR_ALERT | ERR_FATAL;
}
}
return 0;
}
#endif
#ifdef TCP_USER_TIMEOUT
/* parse the "tcp-ut" server keyword */
static int srv_parse_tcp_ut(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
@ -235,6 +286,9 @@ static struct bind_kw_list bind_kws = { "TCP", { }, {
#ifdef TCP_MAXSEG
{ "mss", bind_parse_mss, 1 }, /* set MSS of listening socket */
#endif
#if defined(__linux__) && defined(TCP_MD5SIG)
{ "tcp-md5sig", bind_parse_tcp_md5sig, 1 }, /* set TCP MD5 signature password */
#endif
#ifdef TCP_USER_TIMEOUT
{ "tcp-ut", bind_parse_tcp_ut, 1 }, /* set User Timeout on listening socket */
#endif
@ -264,6 +318,9 @@ static struct bind_kw_list bind_kws = { "TCP", { }, {
INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws);
static struct srv_kw_list srv_kws = { "TCP", { }, {
#if defined(__linux__) && defined(TCP_MD5SIG)
{ "tcp-md5sig", srv_parse_tcp_md5sig, 1, 1, 0 }, /* set TCP MD5 signature password on server */
#endif
#ifdef TCP_USER_TIMEOUT
{ "tcp-ut", srv_parse_tcp_ut, 1, 1, 0 }, /* set TCP user timeout on server */
#endif

View File

@ -2073,6 +2073,8 @@ struct bind_conf *bind_conf_alloc(struct proxy *fe, const char *file,
bind_conf->rhttp_srvname = NULL;
bind_conf->tcp_md5sig = NULL;
return bind_conf;
err:

View File

@ -531,6 +531,25 @@ int tcp_connect_server(struct connection *conn, int flags)
setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
#endif
#if defined(__linux__) && defined(TCP_MD5SIG)
/* if it fails, the connection will fail, so reported an error */
if (srv && srv->tcp_md5sig) {
struct tcp_md5sig md5;
if (conn->dst->ss_family == AF_INET)
memcpy(&md5.tcpm_addr, (struct sockaddr_in *)conn->dst, sizeof(struct sockaddr_in));
else
memcpy(&md5.tcpm_addr, (struct sockaddr_in6 *)conn->dst, sizeof(struct sockaddr_in6));
strlcpy2((char*)md5.tcpm_key, srv->tcp_md5sig, sizeof(md5.tcpm_key));
md5.tcpm_keylen = strlen(srv->tcp_md5sig);
if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5, sizeof(md5)) < 0) {
conn->flags |= CO_FL_ERROR;
return SF_ERR_SRVCL;
}
}
#endif
#ifdef TCP_USER_TIMEOUT
/* there is not much more we can do here when it fails, it's still minor */
if (srv && srv->tcp_ut)
@ -712,6 +731,24 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
}
}
#endif
#if defined(__linux__) && defined(TCP_MD5SIG)
if (listener->bind_conf->tcp_md5sig) {
struct tcp_md5sig md5;
if (listener->rx.addr.ss_family == AF_INET)
memcpy(&md5.tcpm_addr, (struct sockaddr_in *)&listener->rx.addr, sizeof(struct sockaddr_in));
else
memcpy(&md5.tcpm_addr, (struct sockaddr_in6 *)&listener->rx.addr, sizeof(struct sockaddr_in6));
strlcpy2((char*)md5.tcpm_key, listener->bind_conf->tcp_md5sig, sizeof(md5.tcpm_key));
md5.tcpm_keylen = strlen(listener->bind_conf->tcp_md5sig);
if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5, sizeof(md5)) < 0) {
chunk_appendf(msg, "%scannot set TCP MD5 signature, (%s)", msg->data ? ", " : "",
strerror(errno));
err = ERR_ALERT | ERR_ABORT;
}
}
#endif
#if defined(TCP_USER_TIMEOUT)
if (listener->bind_conf->tcp_ut) {
if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT,

View File

@ -418,6 +418,7 @@ void deinit_proxy(struct proxy *p)
LIST_DELETE(&bind_conf->by_fe);
free(bind_conf->guid_prefix);
free(bind_conf->rhttp_srvname);
free(bind_conf->tcp_md5sig);
#ifdef USE_QUIC
free(bind_conf->quic_cc_algo);
#endif

View File

@ -2972,6 +2972,10 @@ void srv_settings_cpy(struct server *srv, const struct server *src, int srv_tmpl
#if defined(USE_OPENSSL)
srv_ssl_settings_cpy(srv, src);
#endif
#ifdef TCP_MD5SIG
if (src->tcp_md5sig != NULL)
srv->tcp_md5sig = strdup(src->tcp_md5sig);
#endif
#ifdef TCP_USER_TIMEOUT
srv->tcp_ut = src->tcp_ut;
#endif
@ -3114,6 +3118,7 @@ void srv_free_params(struct server *srv)
free(srv->pool_conn_name);
release_sample_expr(srv->pool_conn_name_expr);
free(srv->resolvers_id);
free(srv->tcp_md5sig);
free(srv->addr_node.key);
free(srv->lb_nodes);
counters_be_shared_drop(srv->counters.shared);