diff --git a/doc/configuration.txt b/doc/configuration.txt index d98584d46..509ffbda8 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 + 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, + 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 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 : 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 + 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, 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 May be used in the following contexts: tcp, http, log, peers, ring diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h index 70f6c4ed1..37840790e 100644 --- a/include/haproxy/listener-t.h +++ b/include/haproxy/listener-t.h @@ -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 */ diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index 57642a031..f74f46f06 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -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 */ diff --git a/reg-tests/connection/tcp_md5_signature.vtc b/reg-tests/connection/tcp_md5_signature.vtc new file mode 100644 index 000000000..8ea214b8c --- /dev/null +++ b/reg-tests/connection/tcp_md5_signature.vtc @@ -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 diff --git a/src/cfgparse-tcp.c b/src/cfgparse-tcp.c index e60a18a54..0265e7d19 100644 --- a/src/cfgparse-tcp.c +++ b/src/cfgparse-tcp.c @@ -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 diff --git a/src/listener.c b/src/listener.c index 14fb55f1a..9d4232e8e 100644 --- a/src/listener.c +++ b/src/listener.c @@ -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: diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 0de67c4eb..1e568fd86 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -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, diff --git a/src/proxy.c b/src/proxy.c index e3a56af4c..27f629e92 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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 diff --git a/src/server.c b/src/server.c index 8cad9ca04..c5d29a30a 100644 --- a/src/server.c +++ b/src/server.c @@ -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);