MINOR: spoe/checks: Add support for SPOP health checks

A new "option spop-check" statement has been added to enable server health
checks based on SPOP HELLO handshake. SPOP is the protocol used by SPOE filters
to talk to servers.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index acd570d..7b05727 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -5118,6 +5118,34 @@
 			if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
 				goto out;
 		}
+		else if (!strcmp(args[1], "spop-check")) {
+			if (curproxy == &defproxy) {
+				Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n",
+				      file, linenum, args[0], args[1]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+			if (curproxy->cap & PR_CAP_FE) {
+				Alert("parsing [%s:%d] : '%s %s' not allowed in 'frontend' and 'listen' sections.\n",
+				      file, linenum, args[0], args[1]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+
+			/* use SPOE request to check servers' health */
+			free(curproxy->check_req);
+			curproxy->check_req = NULL;
+			curproxy->options2 &= ~PR_O2_CHK_ANY;
+			curproxy->options2 |= PR_O2_SPOP_CHK;
+
+			if (prepare_spoe_healthcheck_request(&curproxy->check_req, &curproxy->check_len)) {
+				Alert("parsing [%s:%d] : failed to prepare SPOP healthcheck request.\n", file, linenum);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+			if (alertif_too_many_args_idx(0, 1, file, linenum, args, &err_code))
+				goto out;
+		}
 		else if (!strcmp(args[1], "tcp-check")) {
 			/* use raw TCPCHK send/expect to check servers' health */
 			if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
diff --git a/src/checks.c b/src/checks.c
index 65d0037..84a0f58 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -1317,6 +1317,26 @@
 		}
 		break;
 
+	case PR_O2_SPOP_CHK: {
+		unsigned int framesz;
+		char	     err[HCHK_DESC_LEN];
+
+		if (!done && check->bi->i < 4)
+			goto wait_more_data;
+
+		memcpy(&framesz, check->bi->data, 4);
+		framesz = ntohl(framesz);
+
+		if (!done && check->bi->i < (4+framesz))
+		    goto wait_more_data;
+
+		if (!handle_spoe_healthcheck_response(check->bi->data+4, framesz, err, HCHK_DESC_LEN-1))
+			set_server_check_status(check, HCHK_STATUS_L7OKD, "SPOA server is ok");
+		else
+			set_server_check_status(check, HCHK_STATUS_L7STS, err);
+		break;
+	}
+
 	default:
 		/* for other checks (eg: pure TCP), delegate to the main task */
 		break;
diff --git a/src/flt_spoe.c b/src/flt_spoe.c
index 1ebdbda..12e589e 100644
--- a/src/flt_spoe.c
+++ b/src/flt_spoe.c
@@ -414,6 +414,7 @@
 #define VERSION_KEY                "version"
 #define MAX_FRAME_SIZE_KEY         "max-frame-size"
 #define CAPABILITIES_KEY           "capabilities"
+#define HEALTHCHECK_KEY            "healthcheck"
 #define STATUS_CODE_KEY            "status-code"
 #define MSG_KEY                    "message"
 
@@ -1075,6 +1076,70 @@
 	return idx;
 }
 
+/* This function is used in cfgparse.c and declared in proto/checks.h. It
+ * prepare the request to send to agents during a healthcheck. It returns 0 on
+ * success and -1 if an error occurred. */
+int
+prepare_spoe_healthcheck_request(char **req, int *len)
+{
+	struct appctx a;
+	char          *frame, buf[global.tune.bufsize];
+	unsigned int  framesz;
+	int	      idx;
+
+	memset(&a, 0, sizeof(a));
+	memset(buf, 0, sizeof(buf));
+	APPCTX_SPOE(&a).max_frame_size = global.tune.bufsize;
+
+	frame = buf+4;
+	idx = prepare_spoe_hahello_frame(&a, frame, global.tune.bufsize-4);
+	if (idx <= 0)
+		return -1;
+	if (idx + SLEN(HEALTHCHECK_KEY) + 1 > global.tune.bufsize-4)
+		return -1;
+
+	/* "healthcheck" K/V item */
+	idx += encode_spoe_string(HEALTHCHECK_KEY, SLEN(HEALTHCHECK_KEY), frame+idx);
+	frame[idx++] = (SPOE_DATA_T_BOOL | SPOE_DATA_FL_TRUE);
+
+	framesz = htonl(idx);
+	memcpy(buf, (char *)&framesz, 4);
+
+	if ((*req = malloc(idx+4)) == NULL)
+		return -1;
+	memcpy(*req, buf, idx+4);
+	*len = idx+4;
+	return 0;
+}
+
+/* This function is used in checks.c and declared in proto/checks.h. It decode
+ * the response received from an agent during a healthcheck. It returns 0 on
+ * success and -1 if an error occurred. */
+int
+handle_spoe_healthcheck_response(char *frame, size_t size, char *err, int errlen)
+{
+	struct appctx a;
+	int           r;
+
+	memset(&a, 0, sizeof(a));
+	APPCTX_SPOE(&a).max_frame_size = global.tune.bufsize;
+
+	if (handle_spoe_agentdiscon_frame(&a, frame, size) != 0)
+		goto error;
+	if ((r = handle_spoe_agenthello_frame(&a, frame, size)) <= 0) {
+		if (r == 0)
+			spoe_status_code = SPOE_FRM_ERR_INVALID;
+		goto error;
+	}
+
+	return 0;
+
+  error:
+	if (spoe_status_code >= SPOE_FRM_ERRS)
+		spoe_status_code = SPOE_FRM_ERR_UNKNOWN;
+	strncpy(err, spoe_frm_err_reasons[spoe_status_code], errlen);
+	return -1;
+}
 
 /********************************************************************
  * Functions that manage the SPOE applet