[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:
parent
bf2886274c
commit
2492d5b4d6
@ -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
|
||||
|
@ -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",""}},
|
||||
|
31
src/client.c
31
src/client.c
@ -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 */
|
||||
|
125
src/proto_http.c
125
src/proto_http.c
@ -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 },
|
||||
|
Loading…
x
Reference in New Issue
Block a user