]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: tcp: Support TCP keepalive parameters customization
authorMIZUTA Takeshi <mizuta.takeshi@fujitsu.com>
Thu, 9 Jul 2020 02:13:20 +0000 (11:13 +0900)
committerWilly Tarreau <w@1wt.eu>
Thu, 9 Jul 2020 03:22:16 +0000 (05:22 +0200)
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
include/haproxy/proxy-t.h
src/cfgparse-listen.c
src/proto_tcp.c
src/proxy.c
src/session.c

index 73bb2f4a05169fe003518cbb17a32a7639fdcf3d..28630ba3fe107c45fa620809f7b641f9bdc01576 100644 (file)
@@ -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 <name> len <length>
              about logging.
 
 
+clitcpka-cnt <count>
+  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 :
+    <count>   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 <timeout>
+  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 :
+    <timeout> 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 <timeout>
+  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 :
+    <timeout> 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 <algorithm> ...
 compression type <mime type> ...
 compression offload
@@ -9407,6 +9461,54 @@ source <addr>[:<port>] [interface <name>]
              the Linux kernel on www.balabit.com, the "bind" keyword.
 
 
+srvtcpka-cnt <count>
+  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 :
+    <count>   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 <timeout>
+  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 :
+    <timeout> 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 <timeout>
+  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 :
+    <timeout> 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 } <cond>
   Enable statistics admin level if/unless a condition is matched
   May be used in sections :   defaults | frontend | listen | backend
index 20cc3c9e16df57f63692a8b7a9f54c4c8d630503..c7e499930cae8df95c849c01c7862e02f499222e 100644 (file)
@@ -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) */
index 34cf346ab8952cf0fa7ead695f005ee7a55b3ae5..df4021d236e6955c9240a3f96a6fbdc744c59739 100644 (file)
@@ -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) {
index 9f0309672aa8188585e9f9ea744dd1c8a823e779..0fdb9440af2ed9ae956149fe49cb04769de095b6 100644 (file)
@@ -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
index a475095428c6a2b09e90050094711175fa26d03d..a03cbd043e27e0342b60982e4b2157924bbd5190 100644 (file)
@@ -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 <px> 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 },
 }};
 
index 02b021201abb2ee519bbe056c98578db260ec664..51380d638ca88cf980fa56a04964b1f4f4ce034a 100644 (file)
@@ -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;