[MEDIUM] New option http_proxy

Hello,

You will find attached an updated release of previously submitted patch.
It polish some part and extend ACL engine to match IP and PORT parsed in
HTTP request. (and take care of comments made by Willy ! ;))

Best regards,
Alexandre
This commit is contained in:
Alexandre Cassen 2007-11-29 15:43:32 +01:00 committed by Willy Tarreau
parent 3168223a7b
commit 5eb1a9033a
9 changed files with 248 additions and 17 deletions

View File

@ -275,6 +275,7 @@ option httpclose X X X X
option httplog X X X X
option logasap X X X -
option nolinger X X X X
option http_proxy X X X X
option persist X - X X
option redispatch X - X X
option smtpchk X - X X
@ -539,6 +540,15 @@ url_reg <regex>
used any time, but it is important to remember that regex matching is slower
than other methods. See also "path_reg" and all "url_" criteria.
url_ip <ip_address>
Applies to the IP address parsed in HTTP request. It can be used to
prevent access to certain resources such as local network. It is useful
with option 'http_proxy'.
url_port <integer>
Applies to the port parsed in HTTP request. It can be used to
prevent access to certain resources. It is useful with option 'http_proxy'.
hdr <string>
hdr(header) <string>
Note: all the "hdr*" matching criteria either apply to all headers, or to a

View File

@ -0,0 +1,53 @@
#
# demo config for Proxy mode
#
global
maxconn 20000
ulimit-n 16384
log 127.0.0.1 local0
uid 200
gid 200
chroot /var/empty
nbproc 4
daemon
frontend test-proxy
bind 192.168.200.10:8080
mode http
log global
option httplog
option dontlognull
option httpclose
option nolinger
option http_proxy
maxconn 8000
clitimeout 30000
# layer3: Valid users
acl allow_host src 192.168.200.150/32
block if !allow_host
# layer7: prevent private network relaying
acl forbidden_dst url_ip 192.168.0.0/24
acl forbidden_dst url_ip 172.16.0.0/12
acl forbidden_dst url_ip 10.0.0.0/8
block if forbidden_dst
default_backend test-proxy-srv
backend test-proxy-srv
mode http
contimeout 5000
srvtimeout 5000
retries 2
option nolinger
option http_proxy
# layer7: Only GET method is valid
acl valid_method method GET
block if !valid_method
# layer7: protect bad reply
rspdeny ^Content-Type:[\ ]*audio/mp3

View File

@ -132,6 +132,11 @@ struct sockaddr_in *str2sa(char *str);
*/
int str2net(const char *str, struct in_addr *addr, struct in_addr *mask);
/*
* Resolve destination server from URL. Convert <str> to a sockaddr_in*.
*/
int url2sa(const char *url, int ulen, struct sockaddr_in *addr);
/* will try to encode the string <string> replacing all characters tagged in
* <map> with the hexadecimal representation of their ASCII-code (2 digits)
* prefixed by <escape>, and will store the result between <start> (included)

View File

@ -101,6 +101,7 @@
#define PR_O_TCPSPLICE 0x08000000 /* delegate data transfer to linux kernel's tcp_splice */
#define PR_O_CONTSTATS 0x10000000 /* continous counters */
#define PR_O_HTTP_PROXY 0x20000000 /* Enable session to use HTTP proxy operations */
/* This structure is used to apply fast weighted round robin on a server group */
struct fwrr_group {

View File

@ -936,6 +936,10 @@ int assign_server(struct session *s)
return SRV_STATUS_INTERNAL;
}
}
else if (s->be->options & PR_O_HTTP_PROXY) {
if (!s->srv_addr.sin_addr.s_addr)
return SRV_STATUS_NOSRV;
}
else if (!*(int *)&s->be->dispatch_addr.sin_addr &&
!(s->fe->options & PR_O_TRANSP)) {
return SRV_STATUS_NOSRV;
@ -999,6 +1003,10 @@ int assign_server_address(struct session *s)
return SRV_STATUS_INTERNAL;
}
}
else if (s->be->options & PR_O_HTTP_PROXY) {
/* If HTTP PROXY option is set, then server is already assigned
* during incoming client request parsing. */
}
else {
/* no server and no LB algorithm ! */
return SRV_STATUS_INTERNAL;

View File

@ -97,6 +97,7 @@ static const struct {
{ "keepalive", PR_O_KEEPALIVE, PR_CAP_NONE, 0 },
{ "httpclose", PR_O_HTTP_CLOSE, PR_CAP_FE | PR_CAP_BE, 0 },
{ "nolinger", PR_O_TCP_NOLING, PR_CAP_FE | PR_CAP_BE, 0 },
{ "http_proxy", PR_O_HTTP_PROXY, PR_CAP_FE | PR_CAP_BE, 0 },
{ "logasap", PR_O_LOGASAP, PR_CAP_FE, 0 },
{ "contstats", PR_O_CONTSTATS, PR_CAP_FE, 0 },
{ "abortonclose", PR_O_ABRT_CLOSE, PR_CAP_BE, 0 },
@ -2473,7 +2474,7 @@ int readcfgfile(const char *file)
}
else if (curproxy->cap & PR_CAP_BE &&
((curproxy->mode != PR_MODE_HEALTH) &&
!(curproxy->options & PR_O_TRANSP) &&
!(curproxy->options & (PR_O_TRANSP | PR_O_HTTP_PROXY)) &&
!(curproxy->lbprm.algo & BE_LB_ALGO) &&
(*(int *)&curproxy->dispatch_addr.sin_addr == 0))) {
Alert("parsing %s : %s '%s' has no dispatch address and is not in transparent or balance mode.\n",

View File

@ -522,6 +522,7 @@ acl_fetch_dport(struct proxy *px, struct session *l4, void *l7, int dir,
return 1;
}
/* set test->i to the number of connexions to the proxy */
static int
acl_fetch_dconn(struct proxy *px, struct session *l4, void *l7, int dir,
@ -534,14 +535,14 @@ acl_fetch_dconn(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 = {{ },{
{ "src_port", acl_parse_int, acl_fetch_sport, acl_match_int },
{ "src", acl_parse_ip, acl_fetch_src, acl_match_ip },
{ "dst", acl_parse_ip, acl_fetch_dst, acl_match_ip },
{ "dst_port", acl_parse_int, acl_fetch_dport, acl_match_int },
{ "src_port", acl_parse_int, acl_fetch_sport, acl_match_int },
{ "src", acl_parse_ip, acl_fetch_src, acl_match_ip },
{ "dst", acl_parse_ip, acl_fetch_dst, acl_match_ip },
{ "dst_port", acl_parse_int, acl_fetch_dport, acl_match_int },
#if 0
{ "src_limit", acl_parse_int, acl_fetch_sconn, acl_match_int },
{ "src_limit", acl_parse_int, acl_fetch_sconn, acl_match_int },
#endif
{ "dst_conn", acl_parse_int, acl_fetch_dconn, acl_match_int },
{ "dst_conn", acl_parse_int, acl_fetch_dconn, acl_match_int },
{ NULL, NULL, NULL, NULL },
}};

View File

@ -1476,7 +1476,7 @@ void http_msg_analyzer(struct buffer *buf, struct http_msg *msg, struct hdr_idx
msg->msg_state = HTTP_MSG_ERROR;
return;
}
/*
* manages the client FSM and its socket. BTW, it also tries to handle the
* cookie. It returns 1 if a state has changed (and a resync may be needed),
@ -1908,8 +1908,13 @@ int process_cli(struct session *t)
* may have separate values for ->fe, ->be.
*/
/*
* If HTTP PROXY is set we simply get remote server address
* parsing incoming request.
*/
if ((t->be->options & PR_O_HTTP_PROXY) && !(t->flags & SN_ADDR_SET)) {
url2sa(req->data + msg->sl.rq.u, msg->sl.rq.u_l, &t->srv_addr);
}
/*
* 7: the appsession cookie was looked up very early in 1.2,
@ -4950,6 +4955,57 @@ acl_fetch_url(struct proxy *px, struct session *l4, void *l7, int dir,
return 1;
}
static int
acl_fetch_url_ip(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct http_txn *txn = l7;
if (txn->req.msg_state != HTTP_MSG_BODY)
return 0;
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
/* ensure the indexes are not affected */
return 0;
/* Parse HTTP request */
url2sa(txn->req.sol + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &l4->srv_addr);
test->ptr = (void *)&((struct sockaddr_in *)&l4->srv_addr)->sin_addr;
test->i = AF_INET;
/*
* If we are parsing url in frontend space, we prepare backend stage
* to not parse again the same url ! optimization lazyness...
*/
if (px->options & PR_O_HTTP_PROXY)
l4->flags |= SN_ADDR_SET;
test->flags = ACL_TEST_F_READ_ONLY;
return 1;
}
static int
acl_fetch_url_port(struct proxy *px, struct session *l4, void *l7, int dir,
struct acl_expr *expr, struct acl_test *test)
{
struct http_txn *txn = l7;
if (txn->req.msg_state != HTTP_MSG_BODY)
return 0;
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
/* ensure the indexes are not affected */
return 0;
/* Same optimization as url_ip */
url2sa(txn->req.sol + txn->req.sl.rq.u, txn->req.sl.rq.u_l, &l4->srv_addr);
test->i = ntohs(((struct sockaddr_in *)&l4->srv_addr)->sin_port);
if (px->options & PR_O_HTTP_PROXY)
l4->flags |= SN_ADDR_SET;
test->flags = ACL_TEST_F_READ_ONLY;
return 1;
}
/* 5. Check on HTTP header. A pointer to the beginning of the value is returned.
* This generic function is used by both acl_fetch_chdr() and acl_fetch_shdr().
*/
@ -5186,13 +5242,15 @@ static struct acl_kw_list acl_kws = {{ },{
{ "resp_ver", acl_parse_ver, acl_fetch_stver, acl_match_str },
{ "status", acl_parse_int, acl_fetch_stcode, acl_match_int },
{ "url", acl_parse_str, acl_fetch_url, acl_match_str },
{ "url_beg", acl_parse_str, acl_fetch_url, acl_match_beg },
{ "url_end", acl_parse_str, acl_fetch_url, acl_match_end },
{ "url_sub", acl_parse_str, acl_fetch_url, acl_match_sub },
{ "url_dir", acl_parse_str, acl_fetch_url, acl_match_dir },
{ "url_dom", acl_parse_str, acl_fetch_url, acl_match_dom },
{ "url_reg", acl_parse_reg, acl_fetch_url, acl_match_reg },
{ "url", acl_parse_str, acl_fetch_url, acl_match_str },
{ "url_beg", acl_parse_str, acl_fetch_url, acl_match_beg },
{ "url_end", acl_parse_str, acl_fetch_url, acl_match_end },
{ "url_sub", acl_parse_str, acl_fetch_url, acl_match_sub },
{ "url_dir", acl_parse_str, acl_fetch_url, acl_match_dir },
{ "url_dom", acl_parse_str, acl_fetch_url, acl_match_dom },
{ "url_reg", acl_parse_reg, acl_fetch_url, acl_match_reg },
{ "url_ip", acl_parse_ip, acl_fetch_url_ip, acl_match_ip },
{ "url_port", acl_parse_int, acl_fetch_url_port, acl_match_int },
{ "hdr", acl_parse_str, acl_fetch_chdr, acl_match_str },
{ "hdr_reg", acl_parse_reg, acl_fetch_chdr, acl_match_reg },

View File

@ -202,6 +202,100 @@ int str2net(const char *str, struct in_addr *addr, struct in_addr *mask)
goto out_free;
}
/*
* Parse IP address found in url.
*/
static int url2ip(const char *addr, struct in_addr *dst)
{
int saw_digit, octets, ch;
u_char tmp[4], *tp;
const char *cp = addr;
saw_digit = 0;
octets = 0;
*(tp = tmp) = 0;
while (*addr) {
unsigned char digit = (ch = *addr++) - '0';
if (digit > 9 && ch != '.')
break;
if (digit <= 9) {
u_int new = *tp * 10 + digit;
if (new > 255)
return 0;
*tp = new;
if (!saw_digit) {
if (++octets > 4)
return 0;
saw_digit = 1;
}
} else if (ch == '.' && saw_digit) {
if (octets == 4)
return 0;
*++tp = 0;
saw_digit = 0;
} else
return 0;
}
if (octets < 4)
return 0;
memcpy(&dst->s_addr, tmp, 4);
return addr-cp-1;
}
/*
* Resolve destination server from URL. Convert <str> to a sockaddr_in*.
*/
int url2sa(const char *url, int ulen, struct sockaddr_in *addr)
{
const char *curr = url, *cp = url;
int ret, url_code = 0;
unsigned int http_code = 0;
/* Cleanup the room */
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = 0;
addr->sin_port = 0;
/* Firstly, try to find :// pattern */
while (curr < url+ulen && url_code != 0x3a2f2f) {
url_code = ((url_code & 0xffff) << 8);
url_code += (unsigned char)*curr++;
}
/* Secondly, if :// pattern is found, verify parsed stuff
* before pattern is matching our http pattern.
* If so parse ip address and port in uri.
*
* WARNING: Current code doesn't support dynamic async dns resolver.
*/
if (url_code == 0x3a2f2f) {
while (cp < curr - 3)
http_code = (http_code << 8) + *cp++;
http_code |= 0x20202020; /* Turn everything to lower case */
/* HTTP url matching */
if (http_code == 0x68747470) {
/* We are looking for IP address. If you want to parse and
* resolve hostname found in url, you can use str2sa(), but
* be warned this can slow down global daemon performances
* while handling lagging dns responses.
*/
ret = url2ip(curr, &addr->sin_addr);
if (!ret)
return -1;
curr += ret;
addr->sin_port = (*curr == ':') ? htons(str2uic(++curr)) : htons(80);
}
return 0;
}
return -1;
}
/* will try to encode the string <string> replacing all characters tagged in
* <map> with the hexadecimal representation of their ASCII-code (2 digits)
* prefixed by <escape>, and will store the result between <start> (included)