[MINOR] acl: add HTTP protocol detection (req_proto_http)

Now that we can perform TCP-based content switching, it makes sense
to be able to detect HTTP traffic and act accordingly. We already
have an HTTP decoder, we just have to call it in order to detect HTTP
protocol. Note that since the decoder will automatically fill in the
interesting fields of the HTTP transaction, it would make sense to
use this parsing to extend HTTP matching to TCP.
This commit is contained in:
Willy Tarreau 2009-07-11 00:06:00 +02:00
parent bf2886274c
commit 2492d5b4d6
4 changed files with 120 additions and 45 deletions

View File

@ -4822,6 +4822,13 @@ req_len <integer>
return false only when haproxy is certain that no more data will come in.
This test was designed to be used with TCP request content inspection.
req_proto_http
Returns true when data in the request buffer look like HTTP and correctly
parses as such. It is the same parser as the common HTTP request parser which
is used so there should be no surprizes. This test can be used for instance
to direct HTTP traffic to a given port and HTTPS traffic to another one
using TCP request content inspection rules.
req_ssl_ver <decimal>
Returns true when data in the request buffer look like SSL, with a protocol
version matching the specified range. Both SSLv2 hello messages and SSLv3
@ -5027,6 +5034,7 @@ ACL name Equivalent to Usage
TRUE always_true always match
FALSE always_false never match
LOCALHOST src 127.0.0.1/8 match connection from local host
HTTP req_proto_http match if protocol is valid HTTP
HTTP_1.0 req_ver 1.0 match HTTP version 1.0
HTTP_1.1 req_ver 1.1 match HTTP version 1.1
METH_CONNECT method CONNECT match HTTP CONNECT method

View File

@ -797,6 +797,7 @@ const struct {
{ .name = "TRUE", .expr = {"always_true",""}},
{ .name = "FALSE", .expr = {"always_false",""}},
{ .name = "LOCALHOST", .expr = {"src","127.0.0.1/8",""}},
{ .name = "HTTP", .expr = {"req_proto_http",""}},
{ .name = "HTTP_1.0", .expr = {"req_ver","1.0",""}},
{ .name = "HTTP_1.1", .expr = {"req_ver","1.1",""}},
{ .name = "METH_CONNECT", .expr = {"method","CONNECT",""}},

View File

@ -252,21 +252,23 @@ int event_accept(int fd) {
txn->hdr_idx.v = NULL;
txn->hdr_idx.size = txn->hdr_idx.used = 0;
if (p->mode == PR_MODE_HTTP) {
txn->status = -1;
txn->req.hdr_content_len = 0LL;
txn->rsp.hdr_content_len = 0LL;
txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
txn->req.sol = txn->req.eol = NULL;
txn->req.som = txn->req.eoh = 0; /* relative to the buffer */
txn->rsp.sol = txn->rsp.eol = NULL;
txn->rsp.som = txn->rsp.eoh = 0; /* relative to the buffer */
txn->req.err_pos = txn->rsp.err_pos = -2; /* block buggy requests/responses */
if (p->options2 & PR_O2_REQBUG_OK)
txn->req.err_pos = -1; /* let buggy requests pass */
txn->auth_hdr.len = -1;
/* we always initialize the HTTP structure because we may use it later */
txn->status = -1;
txn->req.hdr_content_len = 0LL;
txn->rsp.hdr_content_len = 0LL;
txn->req.msg_state = HTTP_MSG_RQBEFORE; /* at the very beginning of the request */
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
txn->req.sol = txn->req.eol = NULL;
txn->req.som = txn->req.eoh = 0; /* relative to the buffer */
txn->rsp.sol = txn->rsp.eol = NULL;
txn->rsp.som = txn->rsp.eoh = 0; /* relative to the buffer */
txn->req.err_pos = txn->rsp.err_pos = -2; /* block buggy requests/responses */
txn->auth_hdr.len = -1;
if (p->options2 & PR_O2_REQBUG_OK)
txn->req.err_pos = -1; /* let buggy requests pass */
if (p->mode == PR_MODE_HTTP) {
/* the captures are only used in HTTP frontends */
if (p->nb_req_cap > 0) {
if ((txn->req.cap = pool_alloc2(p->req_cap_pool)) == NULL)
goto out_fail_reqcap; /* no memory */
@ -274,7 +276,6 @@ int event_accept(int fd) {
memset(txn->req.cap, 0, p->nb_req_cap*sizeof(char *));
}
if (p->nb_rsp_cap > 0) {
if ((txn->rsp.cap = pool_alloc2(p->rsp_cap_pool)) == NULL)
goto out_fail_rspcap; /* no memory */

View File

@ -1498,6 +1498,46 @@ void http_msg_analyzer(struct buffer *buf, struct http_msg *msg, struct hdr_idx
return;
}
/* convert an HTTP/0.9 request into an HTTP/1.0 request. Returns 1 if the
* conversion succeeded, 0 in case of error. If the request was already 1.X,
* nothing is done and 1 is returned.
*/
static int http_upgrade_v09_to_v10(struct buffer *req, struct http_msg *msg, struct http_txn *txn)
{
int delta;
char *cur_end;
if (msg->sl.rq.v_l != 0)
return 1;
msg->sol = req->data + msg->som;
cur_end = msg->sol + msg->sl.rq.l;
delta = 0;
if (msg->sl.rq.u_l == 0) {
/* if no URI was set, add "/" */
delta = buffer_replace2(req, cur_end, cur_end, " /", 2);
cur_end += delta;
msg->eoh += delta;
}
/* add HTTP version */
delta = buffer_replace2(req, cur_end, cur_end, " HTTP/1.0\r\n", 11);
msg->eoh += delta;
cur_end += delta;
cur_end = (char *)http_parse_reqline(msg, req->data,
HTTP_MSG_RQMETH,
msg->sol, cur_end + 1,
NULL, NULL);
if (unlikely(!cur_end))
return 0;
/* we have a full HTTP/1.0 request now and we know that
* we have either a CR or an LF at <ptr>.
*/
hdr_idx_set_start(&txn->hdr_idx, msg->sl.rq.l, *cur_end == '\r');
return 1;
}
/* This stream analyser waits for a complete HTTP request. It returns 1 if the
* processing can continue on next analysers, or zero if it either needs more
* data or wants to immediately abort the request (eg: timeout, error, ...). It
@ -1739,36 +1779,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
}
/* 4. We may have to convert HTTP/0.9 requests to HTTP/1.0 */
if (unlikely(msg->sl.rq.v_l == 0)) {
int delta;
char *cur_end;
msg->sol = req->data + msg->som;
cur_end = msg->sol + msg->sl.rq.l;
delta = 0;
if (msg->sl.rq.u_l == 0) {
/* if no URI was set, add "/" */
delta = buffer_replace2(req, cur_end, cur_end, " /", 2);
cur_end += delta;
msg->eoh += delta;
}
/* add HTTP version */
delta = buffer_replace2(req, cur_end, cur_end, " HTTP/1.0\r\n", 11);
msg->eoh += delta;
cur_end += delta;
cur_end = (char *)http_parse_reqline(msg, req->data,
HTTP_MSG_RQMETH,
msg->sol, cur_end + 1,
NULL, NULL);
if (unlikely(!cur_end))
goto return_bad_req;
/* we have a full HTTP/1.0 request now and we know that
* we have either a CR or an LF at <ptr>.
*/
hdr_idx_set_start(&txn->hdr_idx, msg->sl.rq.l, *cur_end == '\r');
}
if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn))
goto return_bad_req;
/* 5: we may need to capture headers */
if (unlikely((s->logs.logwait & LW_REQHDR) && s->fe->req_cap))
@ -4900,6 +4912,57 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir,
return 1;
}
static int
acl_fetch_proto_http(struct proxy *px, struct session *s, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct buffer *req = s->req;
struct http_txn *txn = &s->txn;
struct http_msg *msg = &txn->req;
/* Note: hdr_idx.v cannot be NULL in this ACL because the ACL is tagged
* as a layer7 ACL, which involves automatic allocation of hdr_idx.
*/
if (!s || !req)
return 0;
if (unlikely(msg->msg_state == HTTP_MSG_BODY)) {
/* Already decoded as OK */
test->flags |= ACL_TEST_F_SET_RES_PASS;
return 1;
}
/* Try to decode HTTP request */
if (likely(req->lr < req->r))
http_msg_analyzer(req, msg, &txn->hdr_idx);
if (unlikely(msg->msg_state != HTTP_MSG_BODY)) {
if ((msg->msg_state == HTTP_MSG_ERROR) || (req->flags & BF_FULL)) {
test->flags |= ACL_TEST_F_SET_RES_FAIL;
return 1;
}
/* wait for final state */
test->flags |= ACL_TEST_F_MAY_CHANGE;
return 0;
}
/* OK we got a valid HTTP request. We have some minor preparation to
* perform so that further checks can rely on HTTP tests.
*/
msg->sol = req->data + msg->som;
txn->meth = find_http_meth(&req->data[msg->som], msg->sl.rq.m_l);
if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
s->flags |= SN_REDIRECTABLE;
if (unlikely(msg->sl.rq.v_l == 0) && !http_upgrade_v09_to_v10(req, msg, txn)) {
test->flags |= ACL_TEST_F_SET_RES_FAIL;
return 1;
}
test->flags |= ACL_TEST_F_SET_RES_PASS;
return 1;
}
/************************************************************************/
@ -4908,6 +4971,8 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir,
/* Note: must not be declared <const> as its list will be overwritten */
static struct acl_kw_list acl_kws = {{ },{
{ "req_proto_http", acl_parse_nothing, acl_fetch_proto_http, acl_match_nothing, ACL_USE_L7REQ_PERMANENT },
{ "method", acl_parse_meth, acl_fetch_meth, acl_match_meth, ACL_USE_L7REQ_PERMANENT },
{ "req_ver", acl_parse_ver, acl_fetch_rqver, acl_match_str, ACL_USE_L7REQ_VOLATILE },
{ "resp_ver", acl_parse_ver, acl_fetch_stver, acl_match_str, ACL_USE_L7RTR_VOLATILE },