From b0ed0615b148d1ee261c018011e0770b684d38f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cyril=20Bont=C3=A9?= <cyril.bonte@free.fr>
Date: Mon, 17 Jun 2013 22:36:19 +0200
Subject: [PATCH] Geolocation based on haproxy 1.5-dev19

---
 include/common/chunk.h     |   1 +
 include/proto/acl.h        |   4 ++
 include/proto/proto_http.h |   2 +
 include/proto/proto_tcp.h  |   1 +
 include/types/acl.h        |   1 +
 include/types/proto_http.h |   1 +
 include/types/proxy.h      |  10 ++++
 src/acl.c                  |  63 ++++++++++++++++++++-
 src/cfgparse.c             |  88 +++++++++++++++++++++++++++++
 src/chunk.c                |  50 +++++++++++++++++
 src/haproxy.c              |   7 +++
 src/proto_http.c           | 136 ++++++++++++++++++++++++++++++++++++++++++---
 src/proto_tcp.c            |   5 +-
 13 files changed, 354 insertions(+), 15 deletions(-)

diff --git a/include/common/chunk.h b/include/common/chunk.h
index 18f41af..4e883b5 100644
--- a/include/common/chunk.h
+++ b/include/common/chunk.h
@@ -47,6 +47,7 @@ int chunk_htmlencode(struct chunk *dst, struct chunk *src);
 int chunk_asciiencode(struct chunk *dst, struct chunk *src, char qc);
 int chunk_strcmp(const struct chunk *chk, const char *str);
 int chunk_strcasecmp(const struct chunk *chk, const char *str);
+int chunk_replace(struct chunk *chk, const char *token, const char *value, const int ofs);
 int alloc_trash_buffers(int bufsize);
 struct chunk *get_trash_chunk(void);
 
diff --git a/include/proto/acl.h b/include/proto/acl.h
index 6859b3d..8c7dda5 100644
--- a/include/proto/acl.h
+++ b/include/proto/acl.h
@@ -224,6 +224,10 @@ int acl_match_ip(struct sample *smp, struct acl_pattern *pattern);
  */
 int acl_match_reg(struct sample *smp, struct acl_pattern *pattern);
 
+void *acl_lookup_ip(struct sample *smp, struct acl_expr *expr);
+int init_geolocate(char *filename, struct acl_expr **geolocate, char **err);
+void deinit_geolocate(struct acl_expr *expr);
+
 #endif /* _PROTO_ACL_H */
 
 /*
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 24e3581..315688e 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -117,6 +117,8 @@ struct chunk *http_error_message(struct session *s, int msgnum);
 struct redirect_rule *http_parse_redirect_rule(const char *file, int line, struct proxy *curproxy,
                                                const char **args, char **errmsg);
 
+int smp_fetch_hdr_ip(struct proxy *px, struct session *l4, void *l7, unsigned int opt, const struct arg *args, struct sample *smp);
+
 /* to be used when contents change in an HTTP message */
 #define http_msg_move_end(msg, bytes) do { \
 		unsigned int _bytes = (bytes);	\
diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h
index 4644452..3ac5230 100644
--- a/include/proto/proto_tcp.h
+++ b/include/proto/proto_tcp.h
@@ -38,6 +38,7 @@ int tcp_drain(int fd);
 int tcp_inspect_request(struct session *s, struct channel *req, int an_bit);
 int tcp_inspect_response(struct session *s, struct channel *rep, int an_bit);
 int tcp_exec_req_rules(struct session *s);
+int smp_fetch_src(struct proxy *px, struct session *l4, void *l7, unsigned int opt, const struct arg *args, struct sample *smp);
 
 /* Converts the INET/INET6 source address to a stick_table key usable for table
  * lookups. Returns either NULL if the source cannot be converted (eg: not
diff --git a/include/types/acl.h b/include/types/acl.h
index 42e2e9c..80d7805 100644
--- a/include/types/acl.h
+++ b/include/types/acl.h
@@ -77,6 +77,7 @@ enum {
 	ACL_PAT_F_FROM_FILE   = 1 << 1,       /* pattern comes from a file */
 	ACL_PAT_F_TREE_OK     = 1 << 2,       /* the pattern parser is allowed to build a tree */
 	ACL_PAT_F_TREE        = 1 << 3,       /* some patterns are arranged in a tree */
+	ACL_PAT_F_DICTIONARY  = 1 << 4,       /* some patterns define a dictionary (geolocation) */
 };
 
 /* ACL match methods */
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 1d7c92f..55ad5fe 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -416,6 +416,7 @@ struct http_txn {
 	char *cli_cookie;               /* cookie presented by the client, in capture mode */
 	char *srv_cookie;               /* cookie presented by the server, in capture mode */
 	char *sessid;                   /* the appsession id, if found in the request or in the response */
+	char *geoloc;                   /* geolocation detected for this transaction */
 	int cookie_first_date;          /* if non-zero, first date the expirable cookie was set/seen */
 	int cookie_last_date;           /* if non-zero, last date the expirable cookie was set/seen */
 
diff --git a/include/types/proxy.h b/include/types/proxy.h
index e6bc755..c25abe0 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -173,6 +173,9 @@ enum {
 #define STK_IS_STORE	0x00000002	/* store on request fetch */
 #define STK_ON_RSP	0x00000004	/* store on response fetch */
 
+#define GEOLOC_SRC	0x00000001      /* geolocation based on the client source IP */
+#define GEOLOC_HDR	0x00000002      /* geolocation based on a HTTP header */
+
 struct error_snapshot {
 	struct timeval when;		/* date of this event, (tv_sec == 0) means "never" */
 	unsigned int len;		/* original length of the last invalid request/response */
@@ -333,6 +336,13 @@ struct proxy {
 	unsigned int backlog;			/* force the frontend's listen backlog */
 	unsigned int bind_proc;			/* bitmask of processes using this proxy. 0 = all. */
 
+	int geoloc_options;			/* GEOLOC_SRC, GEOLOC_HDR */
+	char *geoloc_hdr_name;			/* header name used for geolocation if defined */
+	int geoloc_hdr_len;			/* length of the name of the header above */
+	int geoloc_hdr_occ;			/* occurrence number of header above: >0 = from first, <0 = from end, 0=disabled */
+	struct acl_expr *geolocate;		/* special acl expression reserved for the geolocation */
+	struct acl_cond *geolocate_cond;	/* acl condition to meet before applying geolocation */
+
 	/* warning: these structs are huge, keep them at the bottom */
 	struct sockaddr_storage dispatch_addr;	/* the default address to connect to */
 	struct error_snapshot invalid_req, invalid_rep; /* captures of last errors */
diff --git a/src/acl.c b/src/acl.c
index 664ef5c..a37a26e 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -142,7 +142,7 @@ int acl_match_bin(struct sample *smp, struct acl_pattern *pattern)
 /* Lookup a string in the expression's pattern tree. The node is returned if it
  * exists, otherwise NULL.
  */
-static void *acl_lookup_str(struct sample *smp, struct acl_expr *expr)
+void *acl_lookup_str(struct sample *smp, struct acl_expr *expr)
 {
 	/* data are stored in a tree */
 	struct ebmb_node *node;
@@ -424,7 +424,7 @@ int acl_match_ip(struct sample *smp, struct acl_pattern *pattern)
 /* Lookup an IPv4 address in the expression's pattern tree using the longest
  * match method. The node is returned if it exists, otherwise NULL.
  */
-static void *acl_lookup_ip(struct sample *smp, struct acl_expr *expr)
+void *acl_lookup_ip(struct sample *smp, struct acl_expr *expr)
 {
 	struct in_addr *s;
 
@@ -797,14 +797,18 @@ int acl_parse_ip(const char **text, struct acl_pattern *pattern, int *opaque, ch
 		 */
 		pattern->type = SMP_T_IPV4;
 		if (mask + (mask & -mask) == 0 && tree) {
+			int len = (pattern->ptr.str ? strlen(pattern->ptr.str) + 1 : 0); /* optional null terminated string length */
 			mask = mask ? 33 - flsnz(mask & -mask) : 0; /* equals cidr value */
 			/* FIXME: insert <addr>/<mask> into the tree here */
-			node = calloc(1, sizeof(*node) + 4); /* reserve 4 bytes for IPv4 address */
+			node = calloc(1, sizeof(*node) + 4 + len); /* reserve 4 bytes for IPv4 address + optional tring length */
 			if (!node) {
 				memprintf(err, "out of memory while loading IPv4 pattern");
 				return 0;
 			}
 			memcpy(node->key, &pattern->val.ipv4.addr, 4); /* network byte order */
+			if (len)
+				memcpy(node->key + 4, pattern->ptr.str, len);
+
 			node->node.pfx = mask;
 			if (ebmb_insert_prefix(tree, node, 4) != node)
 				free(node); /* was a duplicate */
@@ -965,6 +969,8 @@ static int acl_read_patterns_from_file(struct acl_expr *expr,
 	pattern = NULL;
 	args[1] = "";
 	while (fgets(trash.str, trash.size, file) != NULL) {
+		char *value = NULL;
+
 		line++;
 		c = trash.str;
 
@@ -978,6 +984,14 @@ static int acl_read_patterns_from_file(struct acl_expr *expr,
 
 
 		args[0] = c;
+		if (patflags & ACL_PAT_F_DICTIONARY) {
+			while (*c && *c != '\n' && *c != '\r' && *c != '\t' && *c != ' ')
+				c++;
+			while (*c == ' ' || *c == '\t')
+				*c++ = 0;
+			value = c;
+		}
+
 		while (*c && *c != '\n' && *c != '\r')
 			c++;
 		*c = 0;
@@ -997,6 +1011,9 @@ static int acl_read_patterns_from_file(struct acl_expr *expr,
 		memset(pattern, 0, sizeof(*pattern));
 		pattern->flags = patflags;
 
+		if (value) {
+			pattern->ptr.str = strdup(value);
+		}
 		if (!(pattern->flags & ACL_PAT_F_IGNORE_CASE) &&
 		    (expr->match == acl_match_str || expr->match == acl_match_ip)) {
 			/* we pre-set the data pointer to the tree's head so that functions
@@ -1956,6 +1973,46 @@ int init_acl()
 	return err;
 }
 
+/*
+ * Initialize a specific acl expression used for geolocation.
+ */
+int init_geolocate(char *filename, struct acl_expr **geolocate, char **err) {
+	struct sample_fetch *smp;
+	struct acl_expr *expr;
+
+	smp = find_sample_fetch("src", 3);
+	if (!smp) {
+		memprintf(err, "unable to find internal acl keyword while loading geoloc dictionary");
+		return 0;
+	}
+
+	expr = (struct acl_expr *)calloc(1, sizeof(*expr));
+	if (!expr) {
+		memprintf(err, "out of memory while loading geoloc dictionary");
+		return 0;
+	}
+
+	expr->kw = smp->kw;
+	LIST_INIT(&expr->patterns);
+	expr->pattern_tree = EB_ROOT_UNIQUE;
+	expr->parse = acl_parse_fcts[ACL_MATCH_IP];
+	expr->match = acl_match_fcts[ACL_MATCH_IP];
+	expr->args = empty_arg_list;
+	expr->smp = smp;
+
+	*geolocate = expr;
+
+	return acl_read_patterns_from_file(expr, filename, ACL_PAT_F_FROM_FILE | ACL_PAT_F_DICTIONARY, err);
+}
+
+/*
+ * Purge the gelocation expression and free memory.
+ */
+void deinit_geolocate(struct acl_expr *expr) {
+	prune_acl_expr(expr);
+	free(expr);
+}
+
 /************************************************************************/
 /*       All supported sample fetch functions must be declared here     */
 /************************************************************************/
diff --git a/src/cfgparse.c b/src/cfgparse.c
index b3435ef..fd911dc 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -5626,6 +5626,94 @@ stats_error_parsing:
 				goto out;
 		}
 	}
+	else if (!strcmp(args[0], "geolocate")) {
+		int cur_arg;
+
+		if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL))
+			err_code |= ERR_WARN;
+
+		cur_arg = 1;
+		if (strcmp(args[cur_arg], "src") == 0) {
+			curproxy->geoloc_options = GEOLOC_SRC;
+		} else if (strncmp(args[cur_arg], "hdr_ip(", 7) == 0) {
+			char *name, *end;
+
+			name = args[cur_arg] + 7;
+			while (isspace(*name))
+				name++;
+
+			end = name;
+			while (*end && !isspace(*end) && *end != ',' && *end != ')')
+				end++;
+
+			curproxy->geoloc_options = GEOLOC_HDR;
+			curproxy->geoloc_hdr_name = calloc(1, end - name + 1);
+			curproxy->geoloc_hdr_len = end - name;
+			memcpy(curproxy->geoloc_hdr_name, name, end - name);
+			curproxy->geoloc_hdr_name[end-name] = '\0';
+			curproxy->geoloc_hdr_occ = -1;
+
+			/* now look for an occurrence number */
+			while (isspace(*end))
+				end++;
+			if (*end == ',') {
+				end++;
+				name = end;
+				if (*end == '-')
+					end++;
+				while (isdigit(*end))
+					end++;
+				curproxy->geoloc_hdr_occ = strl2ic(name, end-name);
+			}
+
+			if (curproxy->geoloc_hdr_occ < -MAX_HDR_HISTORY) {
+				Alert("parsing [%s:%d] : hdr_ip(name,num) does not support negative"
+					" occurrences values smaller than %d.\n",
+					file, linenum, MAX_HDR_HISTORY);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+		}
+		else if (!*args[cur_arg]) {
+			Alert("parsing [%s:%d] : '%s' expects 'src' or 'hdr_ip(<hdr>,<occ>)' as argument.\n",
+				file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		cur_arg++;
+		if (*args[cur_arg]) {
+			if (!init_geolocate(args[cur_arg], &curproxy->geolocate, &errmsg)) {
+				Alert("parsing [%s:%d] : %s: error detected while processing geolocation rule : %s\n",
+					file, linenum, args[0], errmsg);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+		}
+		else {
+			Alert("parsing [%s:%d] : '%s' expects <filename> as argument.\n",
+				file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		cur_arg++;
+		if ((strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0)) {
+			if ((curproxy->geolocate_cond = build_acl_cond(file, linenum, curproxy, (const char **)args+cur_arg, &errmsg)) == NULL) {
+				Alert("parsing [%s:%d] : error detected while parsing a '%s' condition : %s.\n",
+				      file, linenum, args[0], errmsg);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+			err_code |= warnif_cond_conflicts(curproxy->geolocate_cond,
+			                                  (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
+			                                  file, linenum);
+		}
+		else if (*args[cur_arg]) {
+			Alert("parsing [%s:%d] : '%s' : Expecting nothing, 'if', or 'unless', got '%s'.\n",
+			      file, linenum, args[0], args[cur_arg]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
 	else {
 		struct cfg_kw_list *kwl;
 		int index;
diff --git a/src/chunk.c b/src/chunk.c
index 9463abb..551f3e4 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -241,6 +241,56 @@ int chunk_strcasecmp(const struct chunk *chk, const char *str)
 	return diff;
 }
 
+
+/*
+ * Replace a token in chunk with a specified value, beginning at the <ofs>
+ * offset in the chunk, respecting the limit of at most chk->size chars.
+ * Returns the new chunk size.
+ */
+int chunk_replace(struct chunk *chk, const char *token, const char *value, const int ofs)
+{
+	int i;
+	int token_len, value_len, delta;
+	char *s;
+	int len = chk->len;
+
+	token_len = strlen(token);
+	if (!token_len)
+		goto out;
+
+	value_len = strlen(value);
+	delta = value_len - token_len;
+	int n = len - token_len;
+
+	i = ofs;
+	s = chk->str + i;
+	while (i <= n) {
+		if ((*s == token[0]) && (memcmp(s, token, token_len) == 0)) {
+			if (delta) {
+				if (len + delta > chk->size)
+					return -1;
+				if (n - i) 
+					memmove(s + value_len, s + token_len, n - i);
+				len += delta;
+				n += delta;
+			}
+			if (value_len) {
+				memcpy(s, value, value_len);
+				/* skip the value inserted to prevent recursion */
+				i += value_len;
+				s += value_len;
+			}
+		} else {
+			i++;
+			s++;
+		}
+	}
+	chk->len = len;
+ out:
+	return len;
+}
+
+
 /*
  * Local variables:
  *  c-indent-level: 8
diff --git a/src/haproxy.c b/src/haproxy.c
index ac9fba1..456d719 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -1091,6 +1091,13 @@ void deinit(void)
 		deinit_stick_rules(&p->storersp_rules);
 		deinit_stick_rules(&p->sticking_rules);
 
+		if (p->geolocate)
+			deinit_geolocate(p->geolocate);
+		if (p->geolocate_cond) {
+			prune_acl_cond(p->geolocate_cond);
+			free(p->geolocate_cond);
+		}
+
 		free(p->appsession_name);
 
 		h = p->req_cap;
diff --git a/src/proto_http.c b/src/proto_http.c
index 9068050..89313a7 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3344,10 +3344,11 @@ http_res_get_intercept_rule(struct proxy *px, struct list *rules, struct session
  * returns non-zero on success, or zero in case of a, irrecoverable error such
  * as too large a request to build a valid response.
  */
-static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *s, struct http_txn *txn)
+static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *s, struct http_txn *txn, int do_geoloc)
 {
 	struct http_msg *msg = &txn->req;
 	const char *msg_fmt;
+	int location_ofs;
 
 	/* build redirect message */
 	switch(rule->code) {
@@ -3371,6 +3372,8 @@ static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *
 
 	if (unlikely(!chunk_strcpy(&trash, msg_fmt)))
 		return 0;
+	
+	location_ofs = trash.len;
 
 	switch(rule->type) {
 	case REDIRECT_TYPE_SCHEME: {
@@ -3499,6 +3502,10 @@ static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *
 		break;
 	}
 
+	if (do_geoloc && chunk_replace(&trash, "${geoloc}", txn->geoloc ? txn->geoloc : "", location_ofs) < 0) {
+		return 0;
+	}
+
 	if (rule->cookie_len) {
 		memcpy(trash.str + trash.len, "\r\nSet-Cookie: ", 14);
 		trash.len += 14;
@@ -3563,6 +3570,81 @@ static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *
 	return 1;
 }
 
+/*
+ * Determine the request geolocation from the session. If both the backend and
+ * the frontend provide a geolocation rule, then the backend is predominant.
+ * Returns 1 if a geolocation rule exists or zero if none is defined.
+ */
+static int geolocate(struct session *s)
+{
+	struct http_txn *txn = &s->txn;
+	int do_geoloc;
+	struct proxy *px;
+
+	do_geoloc = (txn->geoloc != 0); /* The transaction is geolocated, which means a previous rule exists */
+	px = s->be;
+	while (1) {
+		if (px->geolocate) {
+			int ret = 1;
+			if (px->geolocate_cond) {
+				struct acl_cond *cond = px->geolocate_cond;
+				ret = acl_exec_cond(cond, px, s, txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
+
+				ret = acl_pass(ret);
+				if (cond->pol == ACL_COND_UNLESS)
+					ret = !ret;
+			}
+			if (ret) {
+				struct sample smp;
+				smp.type = SMP_TYPES; /* default value used to detect non matches */
+				smp.ctx.a[0] = NULL;
+				if (px->geoloc_options & GEOLOC_SRC) {
+					struct arg args[0];
+					smp_fetch_src(px, s, txn, SMP_OPT_DIR_REQ, args, &smp);
+				}
+				else if (px->geoloc_options & GEOLOC_HDR) {
+					struct arg args[2];
+					args[0].type = ARGT_STR;
+					args[0].data.str.str = px->geoloc_hdr_name;
+					args[0].data.str.len = px->geoloc_hdr_len;
+
+					args[1].type = ARGT_SINT;
+					args[1].data.uint = px->geoloc_hdr_occ;
+
+					smp_fetch_hdr_ip(px, s, txn, SMP_OPT_DIR_REQ, args, &smp);
+				}
+
+				if (smp.type == SMP_T_IPV4 || smp.type == SMP_T_IPV6) {
+					struct ebmb_node *node = NULL;
+					if (!eb_is_empty(&px->geolocate->pattern_tree)) {
+						/* a tree is present, let's check what type it is */
+						node = acl_lookup_ip(&smp, px->geolocate);
+					}
+					if (node) {
+						txn->geoloc = (char *) (node->key + 4);
+					}
+					else {
+						struct acl_pattern *pattern;
+						/* call the match() function for all tests on this value */
+						list_for_each_entry(pattern, &px->geolocate->patterns, list) {
+							if (px->geolocate->match(&smp, pattern)) {
+								txn->geoloc = pattern->ptr.str;
+								break;
+							}
+						}
+					}
+				}
+			}
+			do_geoloc = 1;
+		}
+		if (txn->geoloc || (px == s->fe)) {
+			break;
+		}
+		px = s->fe;
+	}
+	return do_geoloc;
+}
+
 /* This stream analyser runs all HTTP request processing which is common to
  * frontends and backends, which means blocking ACLs, filters, connection-close,
  * reqadd, stats and redirects. This is performed for the designated proxy.
@@ -3579,6 +3661,7 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
 	struct redirect_rule *rule;
 	struct cond_wordlist *wl;
 	int do_stats;
+	int do_geoloc;
 
 	if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
 		/* we need more data */
@@ -3779,6 +3862,8 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
 		goto return_prx_cond;
 	}
 
+	do_geoloc = geolocate(s);
+
 	/* add request headers from the rule sets in the same order */
 	list_for_each_entry(wl, &px->req_add, list) {
 		if (wl->cond) {
@@ -3795,7 +3880,7 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
 	}
 
 	if (http_req_last_rule && http_req_last_rule->action == HTTP_REQ_ACT_REDIR) {
-		if (!http_apply_redirect_rule(http_req_last_rule->arg.redir, s, txn))
+		if (!http_apply_redirect_rule(http_req_last_rule->arg.redir, s, txn, do_geoloc))
 			goto return_bad_req;
 		req->analyse_exp = TICK_ETERNITY;
 		return 1;
@@ -3824,7 +3909,7 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
 			if (!ret)
 				continue;
 		}
-		if (!http_apply_redirect_rule(rule, s, txn))
+		if (!http_apply_redirect_rule(rule, s, txn, do_geoloc))
 			goto return_bad_req;
 
 		req->analyse_exp = TICK_ETERNITY;
@@ -8172,6 +8257,7 @@ unsigned int http_get_hdr(const struct http_msg *msg, const char *hname, int hle
 		hist_ptr -= MAX_HDR_HISTORY;
 	*vptr = ptr_hist[hist_ptr];
 	*vlen = len_hist[hist_ptr];
+
 	return 1;
 }
 
@@ -8327,7 +8413,7 @@ void http_end_txn(struct session *s)
 	txn->uri = NULL;
 	txn->srv_cookie = NULL;
 	txn->cli_cookie = NULL;
-
+	txn->geoloc = NULL;
 	if (txn->req.cap) {
 		struct cap_hdr *h;
 		for (h = s->fe->req_cap; h; h = h->next)
@@ -9339,8 +9425,9 @@ smp_fetch_hdr(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
 
 	smp->type = SMP_T_CSTR;
 	smp->flags |= SMP_F_VOL_HDR;
-	if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.str.str, &smp->data.str.len))
+	if (http_get_hdr(msg, name_str, name_len, idx, occ, ctx, &smp->data.str.str, &smp->data.str.len)) {
 		return 1;
+	}
 
 	smp->flags &= ~SMP_F_NOT_LAST;
 	return 0;
@@ -9398,9 +9485,8 @@ smp_fetch_hdr_val(struct proxy *px, struct session *l4, void *l7, unsigned int o
  * and an optional one of type int to designate a specific occurrence.
  * It returns an IPv4 or IPv6 address.
  */
-static int
-smp_fetch_hdr_ip(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
-                 const struct arg *args, struct sample *smp)
+int smp_fetch_hdr_ip(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
+		     const struct arg *args, struct sample *smp)
 {
 	int ret;
 
@@ -9419,7 +9505,6 @@ smp_fetch_hdr_ip(struct proxy *px, struct session *l4, void *l7, unsigned int op
 				}
 			}
 		}
-
 		/* if the header doesn't match an IP address, fetch next one */
 		if (!(smp->flags & SMP_F_NOT_LAST))
 			return 0;
@@ -9587,6 +9672,35 @@ smp_fetch_base32_src(struct proxy *px, struct session *l4, void *l7, unsigned in
 	return 1;
 }
 
+
+static int
+smp_fetch_geoloc(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
+               const struct arg *args, struct sample *smp)
+{
+	struct http_txn *txn = l7;
+
+	CHECK_HTTP_MESSAGE_FIRST();
+
+	if (!txn->geoloc)
+		return 0;
+
+	strcpy(trash.str, txn->geoloc);
+	smp->type = SMP_T_STR;
+	smp->data.str.str = trash.str;
+	smp->data.str.len = strlen(trash.str);
+
+	smp->flags = 0;
+	return 1;
+}
+
+static int
+acl_match_geoloc(struct sample *smp, struct acl_pattern *pattern) {
+	if (strcmp(pattern->ptr.str, "*") == 0) {
+		return ACL_PAT_PASS;
+	}
+	return acl_match_str(smp, pattern);
+}
+
 static int
 smp_fetch_proto_http(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
                      const struct arg *args, struct sample *smp)
@@ -10125,6 +10239,8 @@ static struct acl_kw_list acl_kws = {{ },{
 	{ "cook_reg",        "req.cook",      acl_parse_reg,     acl_match_reg     },
 	{ "cook_sub",        "req.cook",      acl_parse_str,     acl_match_sub     },
 
+	{ "geoloc",          "geoloc",        acl_parse_str,     acl_match_geoloc  },
+
 	{ "hdr",             "req.hdr",       acl_parse_str,     acl_match_str     },
 	{ "hdr_beg",         "req.hdr",       acl_parse_str,     acl_match_beg     },
 	{ "hdr_dir",         "req.hdr",       acl_parse_str,     acl_match_dir     },
@@ -10207,6 +10323,8 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {{ },{
 	{ "cook_cnt",        smp_fetch_cookie_cnt,     ARG1(0,STR),      NULL,    SMP_T_UINT, SMP_USE_HRQHV },
 	{ "cook_val",        smp_fetch_cookie_val,     ARG1(0,STR),      NULL,    SMP_T_UINT, SMP_USE_HRQHV },
 
+	{ "geoloc",          smp_fetch_geoloc,         0,                NULL,    SMP_T_CSTR, SMP_USE_HRQHV },
+
 	/* hdr is valid in both directions (eg: for "stick ...") but hdr_* are
 	 * only here to match the ACL's name, are request-only and are used for
 	 * ACL compatibility only.
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index bfce6a2..0632792 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -1537,9 +1537,8 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
 /************************************************************************/
 
 /* fetch the connection's source IPv4/IPv6 address */
-static int
-smp_fetch_src(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
-              const struct arg *args, struct sample *smp)
+int smp_fetch_src(struct proxy *px, struct session *l4, void *l7, unsigned int opt,
+		  const struct arg *args, struct sample *smp)
 {
 	switch (l4->si[0].conn->addr.from.ss_family) {
 	case AF_INET:
-- 
1.8.3.1

