From 1c47f85292d60ce314a193b8aa792b04e1e2a0dc Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 9 Jul 2006 08:22:27 +0200 Subject: [PATCH] [MEDIUM] implemented the 'monitor-uri' keyword. It is used to test haproxy's status with an HTTP request to which it will reply with HTTP/1.0 200 OK. --- ROADMAP | 19 ++++++++++++++++++- doc/haproxy-en.txt | 21 +++++++++++++++++++++ doc/haproxy-fr.txt | 22 ++++++++++++++++++++++ include/types/proxy.h | 44 ++++++++++++++++++++++--------------------- src/cfgparse.c | 28 +++++++++++++++++++++++++++ src/proto_http.c | 44 ++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 155 insertions(+), 23 deletions(-) diff --git a/ROADMAP b/ROADMAP index e529eb1fc..f5c638fff 100644 --- a/ROADMAP +++ b/ROADMAP @@ -35,6 +35,12 @@ srv->effective_maxconn = max(srv->maxconn * px->nbsess / px->maxconn, srv->minconn) +1.2.15 : + + monitor-uri : specify an URI for which we will always return 'HTTP/1.0 200' + and never forward nor log it. + + + option ssl-hello-chk : send SSLv3 client hello messages to check the servers + 1.3 : - remove unused STATTIME @@ -75,7 +81,6 @@ - clarify licence by adding a 'MODULE_LICENCE("GPL")' or something equivalent. - - handle half-closed connections better (cli/srv would not distinguish DATA/SHUTR/SHUTW, it would be a session flag which would tell shutr/shutw). Check how it got changed in httpterm. @@ -93,3 +98,15 @@ - verify if it would be worth implementing an epoll_ctl_batch() for Linux + - balance LC/WLC (patch available) + + - option minservers XXX : activates some backup servers when active servers + are insufficient + + - monitor minservers XXX : monitor-net and monitor-uri could report a failure + when the number of active servers is below this threshold. + + - option smtp-chk : use SMTP health checks (avoid logs if possible) + + - new keyword 'check' : check http xxx, check smtp xxx, check ssl-hello + diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt index 14554f2ba..ea7180784 100644 --- a/doc/haproxy-en.txt +++ b/doc/haproxy-en.txt @@ -494,6 +494,27 @@ Example : monitor-net 192.168.1.252/31 # L4 load-balancers on .252 and .253 +When the system executing the checks is located behind a proxy, the monitor-net +keyword cannot be used because haproxy will always see the proxy's address. To +overcome this limitation, version 1.2.15 brought the 'monitor-uri' keyword. It +defines an URI which will not be forwarded nor logged, but for which haproxy +will immediately send an "HTTP/1.0 200 OK" response. This makes it possible to +check the validity of the reverse-proxy->haproxy chain with one request. It can +be used in HTTPS checks in front of an stunnel -> haproxy combination for +instance. Obviously, this keyword is only valid in HTTP mode, otherwise there +is no notion of URI. Note that the method and HTTP versions are simply ignored. + +Example : +--------- + + listen stunnel_backend :8080 + mode http + balance roundrobin + server web1 192.168.1.10:80 check + server web2 192.168.1.11:80 check + monitor-uri /haproxy_test + + 2.3) Limiting the number of simultaneous connections ---------------------------------------------------- The 'maxconn' parameter allows a proxy to refuse connections above a certain diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt index dd02891de..157028abe 100644 --- a/doc/haproxy-fr.txt +++ b/doc/haproxy-fr.txt @@ -520,6 +520,28 @@ Exemple : monitor-net 192.168.1.252/31 # L4 load-balancers on .252 and .253 +Lorsque le système effectuant les tests est situé derrière un proxy, le mot-clé +'monitor-net' n'est pas utilisable du fait que haproxy verra toujours la même +adresse pour le proxy. Pour pallier à cette limitation, la version 1.2.15 a +apporté le mot-clé 'monitor-uri'. Il définit une URI qui ne sera ni retransmise +ni logée, mais pour laquelle haproxy retournera immédiatement une réponse +"HTTP/1.0 200 OK". Cela rend possibles les tests de validité d'une chaîne +reverse-proxy->haproxy en une requête HTTP. Cela peut être utilisé pour valider +une combinaision de stunnel+haproxy à l'aide de tests HTTPS par exemple. Bien +entendu, ce mot-clé n'est valide qu'en mode HTTP, sinon il n'y a pas de notion +d'URI. Noter que la méthode et la version HTTP sont simplement ignorées. + +Exemple : +--------- + + listen stunnel_backend :8080 + mode http + balance roundrobin + server web1 192.168.1.10:80 check + server web2 192.168.1.11:80 check + monitor-uri /haproxy_test + + 2.3) Limitation du nombre de connexions simultanées --------------------------------------------------- Le paramètre "maxconn" permet de fixer la limite acceptable en nombre de diff --git a/include/types/proxy.h b/include/types/proxy.h index 50c16ecad..7a262dab5 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -67,42 +67,44 @@ struct proxy { int state; /* proxy state */ struct sockaddr_in dispatch_addr; /* the default address to connect to */ struct server *srv; /* known servers */ - int srv_act, srv_bck; /* # of running servers */ - int tot_wact, tot_wbck; /* total weights of active and backup servers */ + int srv_act, srv_bck; /* # of running servers */ + int tot_wact, tot_wbck; /* total weights of active and backup servers */ struct server **srv_map; /* the server map used to apply weights */ - int srv_map_sz; /* the size of the effective server map */ - int srv_rr_idx; /* next server to be elected in round robin mode */ + int srv_map_sz; /* the size of the effective server map */ + int srv_rr_idx; /* next server to be elected in round robin mode */ char *cookie_name; /* name of the cookie to look for */ int cookie_len; /* strlen(cookie_name), computed only once */ - char *appsession_name; /* name of the cookie to look for */ + char *appsession_name; /* name of the cookie to look for */ int appsession_name_len; /* strlen(appsession_name), computed only once */ - int appsession_len; /* length of the appsession cookie value to be used */ + int appsession_len; /* length of the appsession cookie value to be used */ int appsession_timeout; CHTbl htbl_proxy; /* Per Proxy hashtable */ char *capture_name; /* beginning of the name of the cookie to capture */ - int capture_namelen; /* length of the cookie name to match */ + int capture_namelen; /* length of the cookie name to match */ 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 clitimeout; /* client I/O timeout (in milliseconds) */ - int srvtimeout; /* server I/O timeout (in milliseconds) */ - int contimeout; /* connect timeout (in milliseconds) */ + char *monitor_uri; /* a special URI to which we respond with HTTP/200 OK */ + int monitor_uri_len; /* length of the string above. 0 if unused */ + int clitimeout; /* client I/O timeout (in milliseconds) */ + int srvtimeout; /* server I/O timeout (in milliseconds) */ + int contimeout; /* connect timeout (in milliseconds) */ char *id; /* proxy id */ - struct list pendconns; /* pending connections with no server assigned yet */ - int nbpend, nbpend_max; /* number of pending connections with no server assigned yet */ - int totpend; /* total number of pending connections on this instance (for stats) */ + struct list pendconns; /* pending connections with no server assigned yet */ + int nbpend, nbpend_max; /* number of pending connections with no server assigned yet */ + int totpend; /* total number of pending connections on this instance (for stats) */ unsigned int nbconn, nbconn_max; /* # of active sessions */ - unsigned int cum_conn; /* cumulated number of processed sessions */ - unsigned int maxconn; /* max # of active sessions */ + unsigned int cum_conn; /* cumulated number of processed sessions */ + unsigned int maxconn; /* max # of active sessions */ unsigned failed_conns, failed_resp; /* failed connect() and responses */ - unsigned failed_secu; /* blocked responses because of security concerns */ + unsigned failed_secu; /* blocked responses because of security concerns */ int conn_retries; /* maximum number of connect retries */ - int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ + int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */ - struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ + struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ struct proxy *next; - struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */ - signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */ - int loglev1, loglev2; /* log level for each server, 7 by default */ + struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */ + signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */ + int loglev1, loglev2; /* log level for each server, 7 by default */ int to_log; /* things to be logged (LW_*) */ struct timeval stop_time; /* date to stop listening, when stopping != 0 */ int nb_reqadd, nb_rspadd; diff --git a/src/cfgparse.c b/src/cfgparse.c index 5e4aa51ee..836dc9aa3 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -521,6 +521,11 @@ int cfg_parse_listen(char *file, int linenum, char **args) curproxy->source_addr = defproxy.source_addr; curproxy->mon_net = defproxy.mon_net; curproxy->mon_mask = defproxy.mon_mask; + + if (defproxy.monitor_uri) + curproxy->monitor_uri = strdup(defproxy.monitor_uri); + curproxy->monitor_uri_len = defproxy.monitor_uri_len; + return 0; } else if (!strcmp(args[0], "defaults")) { /* use this one to assign default values */ @@ -535,6 +540,7 @@ int cfg_parse_listen(char *file, int linenum, char **args) if (defproxy.errmsg.msg502) free(defproxy.errmsg.msg502); if (defproxy.errmsg.msg503) free(defproxy.errmsg.msg503); if (defproxy.errmsg.msg504) free(defproxy.errmsg.msg504); + if (defproxy.monitor_uri) free(defproxy.monitor_uri); /* we cannot free uri_auth because it might already be used */ init_default_instance(); curproxy = &defproxy; @@ -572,6 +578,24 @@ int cfg_parse_listen(char *file, int linenum, char **args) curproxy->mon_net.s_addr &= curproxy->mon_mask.s_addr; return 0; } + else if (!strcmp(args[0], "monitor-uri")) { /* set the URI to intercept */ + if (!*args[1]) { + Alert("parsing [%s:%d] : '%s' expects an URI.\n", + file, linenum, args[0]); + return -1; + } + + if (curproxy->monitor_uri != NULL) + free(curproxy->monitor_uri); + + curproxy->monitor_uri_len = strlen(args[1]) + 2; /* include leading and trailing spaces */ + curproxy->monitor_uri = (char *)calloc(1, curproxy->monitor_uri_len + 1); + memcpy(curproxy->monitor_uri + 1, args[1], curproxy->monitor_uri_len - 2); + curproxy->monitor_uri[curproxy->monitor_uri_len-1] = curproxy->monitor_uri[0] = ' '; + curproxy->monitor_uri[curproxy->monitor_uri_len] = '\0'; + + return 0; + } else if (!strcmp(args[0], "mode")) { /* sets the proxy mode */ if (!strcmp(args[1], "http")) curproxy->mode = PR_MODE_HTTP; else if (!strcmp(args[1], "tcp")) curproxy->mode = PR_MODE_TCP; @@ -1903,6 +1927,10 @@ int readcfgfile(char *file) Warning("parsing %s : client regular expressions will be ignored for listener %s.\n", file, curproxy->id); } + if (curproxy->monitor_uri != NULL) { + Warning("parsing %s : monitor-uri will be ignored for listener %s.\n", + file, curproxy->id); + } } else if (curproxy->mode == PR_MODE_HTTP) { /* HTTP PROXY */ if ((curproxy->cookie_name != NULL) && ((newsrv = curproxy->srv) == NULL)) { diff --git a/src/proto_http.c b/src/proto_http.c index e1a7d1734..d4e97641d 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -50,6 +50,15 @@ #include +/* This is used by remote monitoring */ +const char *HTTP_200 = + "HTTP/1.0 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "

200 OK

\nHAProxy: service ready.\n\n"; + /* Warning: this one is an sprintf() fmt string, with as its only argument */ const char *HTTP_401_fmt = "HTTP/1.0 401 Unauthorized\r\n" @@ -180,7 +189,9 @@ int process_session(struct task *t) s->logs.bytes = s->rep->total; /* let's do a final log if we need it */ - if (s->logs.logwait && (!(s->proxy->options & PR_O_NULLNOLOG) || s->req->total)) + if (s->logs.logwait && + !(s->flags & SN_MONITOR) && + (!(s->proxy->options & PR_O_NULLNOLOG) || s->req->total)) sess_log(s); /* the task MUST not be in the run queue anymore */ @@ -282,6 +293,37 @@ int process_cli(struct session *t) * whatever we want. */ + + /* check if the URI matches the monitor_uri. To speed-up the + * test, we include the leading and trailing spaces in the + * comparison. + */ + if ((t->proxy->monitor_uri_len != 0) && + (t->req_line.len >= t->proxy->monitor_uri_len)) { + char *p = t->req_line.str; + int idx = 0; + + /* skip the method so that we accept any method */ + while (idx < t->req_line.len && p[idx] != ' ') + idx++; + p += idx; + + if (t->req_line.len - idx >= t->proxy->monitor_uri_len && + !memcmp(p, t->proxy->monitor_uri, t->proxy->monitor_uri_len)) { + /* + * We have found the monitor URI + */ + t->flags |= SN_MONITOR; + t->logs.status = 200; + client_retnclose(t, strlen(HTTP_200), HTTP_200); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + } + if (t->proxy->uri_auth != NULL && t->req_line.len >= t->proxy->uri_auth->uri_len + 4) { /* +4 for "GET /" */ if (!memcmp(t->req_line.str + 4,