From b24bc0dfb6cc6136d87d19d29acf5cc68803d49b Mon Sep 17 00:00:00 2001 From: MIZUTA Takeshi Date: Thu, 9 Jul 2020 11:13:20 +0900 Subject: [PATCH] MINOR: tcp: Support TCP keepalive parameters customization It is now possible to customize TCP keepalive parameters. These correspond to the socket options TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL and are valid for the defaults, listen, frontend and backend sections. This patch fixes GitHub issue #670. --- doc/configuration.txt | 102 ++++++++++++++++++++++++ include/haproxy/proxy-t.h | 6 ++ src/cfgparse-listen.c | 8 ++ src/proto_tcp.c | 12 ++- src/proxy.c | 159 ++++++++++++++++++++++++++++++++++++++ src/session.c | 12 ++- 6 files changed, 297 insertions(+), 2 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 73bb2f4a0..28630ba3f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2816,6 +2816,9 @@ bind-process X X X X capture cookie - X X - capture request header - X X - capture response header - X X - +clitcpka-cnt X X X - +clitcpka-idle X X X - +clitcpka-intvl X X X - compression X X X X cookie X - X X declare capture - X X - @@ -2934,6 +2937,9 @@ server - - X X server-state-file-name X - X X server-template - - X X source X - X X +srvtcpka-cnt X - X X +srvtcpka-idle X - X X +srvtcpka-intvl X - X X stats admin - X X X stats auth X X X X stats enable X X X X @@ -3572,6 +3578,54 @@ capture response header len about logging. +clitcpka-cnt + Sets the maximum number of keepalive probes TCP should send before dropping + the connection on the client side. + May be used in sections : defaults | frontend | listen | backend + yes | yes | yes | no + Arguments : + is the maximum number of keepalive probes. + + This keyword corresponds to the socket option TCP_KEEPCNT. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_probes) is used. + + See also : "option clitcpka", "clitcpka-idle", "clitcpka-intvl". + + +clitcpka-idle + Sets the time the connection needs to remain idle before TCP starts sending + keepalive probes, if enabled the sending of TCP keepalive packets on the + client side. + May be used in sections : defaults | frontend | listen | backend + yes | yes | yes | no + Arguments : + is the time the connection needs to remain idle before TCP starts + sending keepalive probes. It is specified in seconds by default, + but can be in any other unit if the number is suffixed by the + unit, as explained at the top of this document. + + This keyword corresponds to the socket option TCP_KEEPIDLE. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_time) is used. + + See also : "option clitcpka", "clitcpka-cnt", "clitcpka-intvl". + + +clitcpka-intvl + Sets the time between individual keepalive probes on the client side. + May be used in sections : defaults | frontend | listen | backend + yes | yes | yes | no + Arguments : + is the time between individual keepalive probes. It is specified + in seconds by default, but can be in any other unit if the number + is suffixed by the unit, as explained at the top of this + document. + + This keyword corresponds to the socket option TCP_KEEPINTVL. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_intvl) is used. + + See also : "option clitcpka", "clitcpka-cnt", "clitcpka-idle". + + compression algo ... compression type ... compression offload @@ -9407,6 +9461,54 @@ source [:] [interface ] the Linux kernel on www.balabit.com, the "bind" keyword. +srvtcpka-cnt + Sets the maximum number of keepalive probes TCP should send before dropping + the connection on the server side. + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + is the maximum number of keepalive probes. + + This keyword corresponds to the socket option TCP_KEEPCNT. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_probes) is used. + + See also : "option srvtcpka", "srvtcpka-idle", "srvtcpka-intvl". + + +srvtcpka-idle + Sets the time the connection needs to remain idle before TCP starts sending + keepalive probes, if enabled the sending of TCP keepalive packets on the + server side. + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + is the time the connection needs to remain idle before TCP starts + sending keepalive probes. It is specified in seconds by default, + but can be in any other unit if the number is suffixed by the + unit, as explained at the top of this document. + + This keyword corresponds to the socket option TCP_KEEPIDLE. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_time) is used. + + See also : "option srvtcpka", "srvtcpka-cnt", "srvtcpka-intvl". + + +srvtcpka-intvl + Sets the time between individual keepalive probes on the server side. + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + is the time between individual keepalive probes. It is specified + in seconds by default, but can be in any other unit if the number + is suffixed by the unit, as explained at the top of this + document. + + This keyword corresponds to the socket option TCP_KEEPINTVL. If this keyword + is not specified, system-wide TCP parameter (tcp_keepalive_intvl) is used. + + See also : "option srvtcpka", "srvtcpka-cnt", "srvtcpka-idle". + + stats admin { if | unless } Enable statistics admin level if/unless a condition is matched May be used in sections : defaults | frontend | listen | backend diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 20cc3c9e1..c7e499930 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -306,6 +306,12 @@ struct proxy { int capture_len; /* length of the string to be captured */ struct uri_auth *uri_auth; /* if non-NULL, the (list of) per-URI authentications */ int max_ka_queue; /* 1+maximum requests in queue accepted for reusing a K-A conn (0=none) */ + int clitcpka_cnt; /* The maximum number of keepalive probes TCP should send before dropping the connection. (client side) */ + int clitcpka_idle; /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes. (client side) */ + int clitcpka_intvl; /* The time (in seconds) between individual keepalive probes. (client side) */ + int srvtcpka_cnt; /* The maximum number of keepalive probes TCP should send before dropping the connection. (server side) */ + int srvtcpka_idle; /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes. (server side) */ + int srvtcpka_intvl; /* The time (in seconds) between individual keepalive probes. (server side) */ int monitor_uri_len; /* length of the string above. 0 if unused */ char *monitor_uri; /* a special URI to which we respond with HTTP/200 OK */ struct list mon_fail_cond; /* list of conditions to fail monitoring requests (chained) */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 34cf346ab..df4021d23 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -277,6 +277,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR; curproxy->max_out_conns = defproxy.max_out_conns; + + curproxy->clitcpka_cnt = defproxy.clitcpka_cnt; + curproxy->clitcpka_idle = defproxy.clitcpka_idle; + curproxy->clitcpka_intvl = defproxy.clitcpka_intvl; } if (curproxy->cap & PR_CAP_BE) { @@ -337,6 +341,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) curproxy->conn_src.tproxy_addr = defproxy.conn_src.tproxy_addr; #endif curproxy->load_server_state_from_file = defproxy.load_server_state_from_file; + + curproxy->srvtcpka_cnt = defproxy.srvtcpka_cnt; + curproxy->srvtcpka_idle = defproxy.srvtcpka_idle; + curproxy->srvtcpka_intvl = defproxy.srvtcpka_intvl; } if (curproxy->cap & PR_CAP_FE) { diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 9f0309672..0fdb9440a 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -379,9 +379,19 @@ int tcp_connect_server(struct connection *conn, int flags) return SF_ERR_INTERNAL; } - if (be->options & PR_O_TCP_SRV_KA) + if (be->options & PR_O_TCP_SRV_KA) { setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)); + if (be->srvtcpka_cnt) + setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &be->srvtcpka_cnt, sizeof(be->srvtcpka_cnt)); + + if (be->srvtcpka_idle) + setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &be->srvtcpka_idle, sizeof(be->srvtcpka_idle)); + + if (be->srvtcpka_intvl) + setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &be->srvtcpka_intvl, sizeof(be->srvtcpka_intvl)); + } + /* allow specific binding : * - server-specific at first * - proxy-specific next diff --git a/src/proxy.c b/src/proxy.c index a47509542..a03cbd043 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -570,6 +570,159 @@ proxy_parse_retry_on(char **args, int section, struct proxy *curpx, return 0; } +/* This function parses "{cli|srv}tcpka-cnt" statements */ +static int proxy_parse_tcpka_cnt(char **args, int section, struct proxy *proxy, + struct proxy *defpx, const char *file, int line, + char **err) +{ + int retval; + char *res; + unsigned int tcpka_cnt; + + retval = 0; + + if (*args[1] == 0) { + memprintf(err, "'%s' expects an integer value", args[0]); + return -1; + } + + tcpka_cnt = strtol(args[1], &res, 0); + if (*res) { + memprintf(err, "'%s' : unexpected character '%c' in integer value '%s'", args[0], *res, args[1]); + return -1; + } + + if (!strcmp(args[0], "clitcpka-cnt")) { + if (!(proxy->cap & PR_CAP_FE)) { + memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->clitcpka_cnt = tcpka_cnt; + } else if (!strcmp(args[0], "srvtcpka-cnt")) { + if (!(proxy->cap & PR_CAP_BE)) { + memprintf(err, "%s will be ignored because %s '%s' has no backend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->srvtcpka_cnt = tcpka_cnt; + } else { + /* unreachable */ + memprintf(err, "'%s': unknown keyword", args[0]); + return -1; + } + + return retval; +} + +/* This function parses "{cli|srv}tcpka-idle" statements */ +static int proxy_parse_tcpka_idle(char **args, int section, struct proxy *proxy, + struct proxy *defpx, const char *file, int line, + char **err) +{ + int retval; + const char *res; + unsigned int tcpka_idle; + + retval = 0; + + if (*args[1] == 0) { + memprintf(err, "'%s' expects an integer value", args[0]); + return -1; + } + res = parse_time_err(args[1], &tcpka_idle, TIME_UNIT_S); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", + args[1], args[0]); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", + args[1], args[0]); + return -1; + } + else if (res) { + memprintf(err, "unexpected character '%c' in argument to <%s>.\n", *res, args[0]); + return -1; + } + + if (!strcmp(args[0], "clitcpka-idle")) { + if (!(proxy->cap & PR_CAP_FE)) { + memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->clitcpka_idle = tcpka_idle; + } else if (!strcmp(args[0], "srvtcpka-idle")) { + if (!(proxy->cap & PR_CAP_BE)) { + memprintf(err, "%s will be ignored because %s '%s' has no backend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->srvtcpka_idle = tcpka_idle; + } else { + /* unreachable */ + memprintf(err, "'%s': unknown keyword", args[0]); + return -1; + } + + return retval; +} + +/* This function parses "{cli|srv}tcpka-intvl" statements */ +static int proxy_parse_tcpka_intvl(char **args, int section, struct proxy *proxy, + struct proxy *defpx, const char *file, int line, + char **err) +{ + int retval; + const char *res; + unsigned int tcpka_intvl; + + retval = 0; + + if (*args[1] == 0) { + memprintf(err, "'%s' expects an integer value", args[0]); + return -1; + } + res = parse_time_err(args[1], &tcpka_intvl, TIME_UNIT_S); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to '%s' (maximum value is 2147483647 ms or ~24.8 days)", + args[1], args[0]); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to '%s' (minimum non-null value is 1 ms)", + args[1], args[0]); + return -1; + } + else if (res) { + memprintf(err, "unexpected character '%c' in argument to <%s>.\n", *res, args[0]); + return -1; + } + + if (!strcmp(args[0], "clitcpka-intvl")) { + if (!(proxy->cap & PR_CAP_FE)) { + memprintf(err, "%s will be ignored because %s '%s' has no frontend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->clitcpka_intvl = tcpka_intvl; + } else if (!strcmp(args[0], "srvtcpka-intvl")) { + if (!(proxy->cap & PR_CAP_BE)) { + memprintf(err, "%s will be ignored because %s '%s' has no backend capability", + args[0], proxy_type_str(proxy), proxy->id); + retval = 1; + } + proxy->srvtcpka_intvl = tcpka_intvl; + } else { + /* unreachable */ + memprintf(err, "'%s': unknown keyword", args[0]); + return -1; + } + + return retval; +} + /* This function inserts proxy into the tree of known proxies. The proxy's * name is used as the storing key so it must already have been initialized. */ @@ -1675,6 +1828,12 @@ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_LISTEN, "max-keep-alive-queue", proxy_parse_max_ka_queue }, { CFG_LISTEN, "declare", proxy_parse_declare }, { CFG_LISTEN, "retry-on", proxy_parse_retry_on }, + { CFG_LISTEN, "clitcpka-cnt", proxy_parse_tcpka_cnt }, + { CFG_LISTEN, "clitcpka-idle", proxy_parse_tcpka_idle }, + { CFG_LISTEN, "clitcpka-intvl", proxy_parse_tcpka_intvl }, + { CFG_LISTEN, "srvtcpka-cnt", proxy_parse_tcpka_cnt }, + { CFG_LISTEN, "srvtcpka-idle", proxy_parse_tcpka_idle }, + { CFG_LISTEN, "srvtcpka-intvl", proxy_parse_tcpka_intvl }, { 0, NULL, NULL }, }}; diff --git a/src/session.c b/src/session.c index 02b021201..51380d638 100644 --- a/src/session.c +++ b/src/session.c @@ -224,9 +224,19 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr if (l->addr.ss_family == AF_INET || l->addr.ss_family == AF_INET6) { setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)); - if (p->options & PR_O_TCP_CLI_KA) + if (p->options & PR_O_TCP_CLI_KA) { setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); + if (p->clitcpka_cnt) + setsockopt(cfd, IPPROTO_TCP, TCP_KEEPCNT, &p->clitcpka_cnt, sizeof(p->clitcpka_cnt)); + + if (p->clitcpka_idle) + setsockopt(cfd, IPPROTO_TCP, TCP_KEEPIDLE, &p->clitcpka_idle, sizeof(p->clitcpka_idle)); + + if (p->clitcpka_intvl) + setsockopt(cfd, IPPROTO_TCP, TCP_KEEPINTVL, &p->clitcpka_intvl, sizeof(p->clitcpka_intvl)); + } + if (p->options & PR_O_TCP_NOLING) fdtab[cfd].linger_risk = 1;