[master] b06cdbbab Add bans on obj.ttl, obj.age, obj.grace and obj.keep

Nils Goroll nils.goroll at uplex.de
Mon Feb 18 09:49:03 UTC 2019


commit b06cdbbab4099a351e188106add56b5e1eb0b927
Author: Nils Goroll <nils.goroll at uplex.de>
Date:   Fri Feb 15 14:49:22 2019 +0100

    Add bans on obj.ttl, obj.age, obj.grace and obj.keep
    
    Also add a duration argument type and the operators >, >=, < and <=
    for use with it (besides the existing == and !=).
    
    obj.ttl and obj.age are compared relative to the time the ban was
    issued, as required by the fact that bans are evaluated some arbitrary
    time after they are created. For this reason, the ban_dups parameter
    has no effect on obj.ttl and obj.age bans, duplicates of bans using
    these fields are never removed.
    
    obj.grace and obj.keep are compared as absolute values.
    
    In an effort to support The Proprietary Stevedore (tm) [or any other storage
    engine supporting persistent bans - if any?], the existing ban serialisation
    format is preserved and upgrades _should_ work seamlessly. But downgrades won't.
    
    Using the BANS_HAS_* macros, we try to make the relationship between the ban
    serialisation in cache_ban_build.c and access in cache_ban.c more obvious.
    
    We now generate all fields and operators from table files as well as
    the definition of allowed operators for fields. The user help text
    about allowed operators is generated at init time.

diff --git a/bin/varnishd/cache/cache_ban.c b/bin/varnishd/cache/cache_ban.c
index a8c9a08c1..122fd9aab 100644
--- a/bin/varnishd/cache/cache_ban.c
+++ b/bin/varnishd/cache/cache_ban.c
@@ -31,6 +31,7 @@
 #include "config.h"
 
 #include <pcre.h>
+#include <stdio.h>
 
 #include "cache_varnishd.h"
 #include "cache_ban.h"
@@ -40,6 +41,10 @@
 #include "vend.h"
 #include "vmb.h"
 
+/* cache_ban_build.c */
+void BAN_Build_Init(void);
+void BAN_Build_Fini(void);
+
 struct lock ban_mtx;
 int ban_shutdown;
 struct banhead_s ban_head = VTAILQ_HEAD_INITIALIZER(ban_head);
@@ -55,9 +60,18 @@ struct ban_test {
 	uint8_t			arg1;
 	const char		*arg1_spec;
 	const char		*arg2;
+	double			arg2_double;
 	const void		*arg2_spec;
 };
 
+static const char * const arg_name[BAN_ARGARRSZ + 1] = {
+#define PVAR(a, b, c) [BAN_ARGIDX(c)] = (a),
+#include "tbl/ban_vars.h"
+	[BAN_ARGARRSZ] = NULL
+};
+
+extern const char * const oper[BAN_OPERARRSZ + 1];
+
 /*--------------------------------------------------------------------
  * Storage handling of bans
  */
@@ -148,6 +162,9 @@ ban_equal(const uint8_t *bs1, const uint8_t *bs2)
 	u = vbe32dec(bs1 + BANS_LENGTH);
 	if (u != vbe32dec(bs2 + BANS_LENGTH))
 		return (0);
+	if (bs1[BANS_FLAGS] & BANS_FLAG_NODEDUP)
+		return (0);
+
 	return (!memcmp(bs1 + BANS_LENGTH, bs2 + BANS_LENGTH, u - BANS_LENGTH));
 }
 
@@ -200,16 +217,25 @@ ban_get_lump(const uint8_t **bs)
 static void
 ban_iter(const uint8_t **bs, struct ban_test *bt)
 {
+	uint64_t dtmp;
 
 	memset(bt, 0, sizeof *bt);
+	bt->arg2_double = nan("");
 	bt->arg1 = *(*bs)++;
-	if (bt->arg1 == BANS_ARG_REQHTTP || bt->arg1 == BANS_ARG_OBJHTTP) {
+	if (BANS_HAS_ARG1_SPEC(bt->arg1)) {
 		bt->arg1_spec = (const char *)*bs;
 		(*bs) += (*bs)[0] + 2;
 	}
+	if (BANS_HAS_ARG2_DOUBLE(bt->arg1)) {
+		dtmp = vbe64dec(ban_get_lump(bs));
+		bt->oper = *(*bs)++;
+
+		memcpy(&bt->arg2_double, &dtmp, sizeof dtmp);
+		return;
+	}
 	bt->arg2 = ban_get_lump(bs);
 	bt->oper = *(*bs)++;
-	if (bt->oper == BANS_OPER_MATCH || bt->oper == BANS_OPER_NMATCH)
+	if (BANS_HAS_ARG2_SPEC(bt->oper))
 		bt->arg2_spec = ban_get_lump(bs);
 }
 
@@ -456,20 +482,32 @@ BAN_Time(const struct ban *b)
  */
 
 int
-ban_evaluate(struct worker *wrk, const uint8_t *bs, struct objcore *oc,
+ban_evaluate(struct worker *wrk, const uint8_t *bsarg, struct objcore *oc,
     const struct http *reqhttp, unsigned *tests)
 {
 	struct ban_test bt;
-	const uint8_t *be;
+	const uint8_t *bs, *be;
 	const char *p;
 	const char *arg1;
+	double darg1, darg2;
+
+	/*
+	 * for ttl and age, fix the point in time such that banning refers to
+	 * the same point in time when the ban is evaluated
+	 *
+	 * for grace/keep, we assume that the absolute values are pola and that
+	 * users will most likely also specify a ttl criterion if they want to
+	 * fix a point in time (such as "obj.ttl > 5h && obj.keep > 3h")
+	 */
 
+	bs = bsarg;
 	be = bs + ban_len(bs);
 	bs += BANS_HEAD_LEN;
 	while (bs < be) {
 		(*tests)++;
 		ban_iter(&bs, &bt);
 		arg1 = NULL;
+		darg1 = darg2 = nan("");
 		switch (bt.arg1) {
 		case BANS_ARG_URL:
 			AN(reqhttp);
@@ -486,18 +524,42 @@ ban_evaluate(struct worker *wrk, const uint8_t *bs, struct objcore *oc,
 		case BANS_ARG_OBJSTATUS:
 			arg1 = HTTP_GetHdrPack(wrk, oc, H__Status);
 			break;
+		case BANS_ARG_OBJTTL:
+			darg1 = oc->ttl + oc->t_origin;
+			darg2 = bt.arg2_double + ban_time(bsarg);
+			break;
+		case BANS_ARG_OBJAGE:
+			darg1 = 0.0 - oc->t_origin;
+			darg2 = 0.0 - (ban_time(bsarg) - bt.arg2_double);
+			break;
+		case BANS_ARG_OBJGRACE:
+			darg1 = oc->grace;
+			darg2 = bt.arg2_double;
+			break;
+		case BANS_ARG_OBJKEEP:
+			darg1 = oc->keep;
+			darg2 = bt.arg2_double;
+			break;
 		default:
 			WRONG("Wrong BAN_ARG code");
 		}
 
 		switch (bt.oper) {
 		case BANS_OPER_EQ:
-			if (arg1 == NULL || strcmp(arg1, bt.arg2))
+			if (arg1 == NULL) {
+				if (isnan(darg1) || darg1 != darg2)
+					return (0);
+			} else if (strcmp(arg1, bt.arg2)) {
 				return (0);
+			}
 			break;
 		case BANS_OPER_NEQ:
-			if (arg1 != NULL && !strcmp(arg1, bt.arg2))
+			if (arg1 == NULL) {
+				if (! isnan(darg1) && darg1 == darg2)
+					return (0);
+			} else if (!strcmp(arg1, bt.arg2)) {
 				return (0);
+			}
 			break;
 		case BANS_OPER_MATCH:
 			if (arg1 == NULL ||
@@ -511,6 +573,30 @@ ban_evaluate(struct worker *wrk, const uint8_t *bs, struct objcore *oc,
 			    0, 0, NULL, 0) >= 0)
 				return (0);
 			break;
+		case BANS_OPER_GT:
+			AZ(arg1);
+			assert(! isnan(darg1));
+			if (!(darg1 > darg2))
+				return (0);
+			break;
+		case BANS_OPER_GTE:
+			AZ(arg1);
+			assert(! isnan(darg1));
+			if (!(darg1 >= darg2))
+				return (0);
+			break;
+		case BANS_OPER_LT:
+			AZ(arg1);
+			assert(! isnan(darg1));
+			if (!(darg1 < darg2))
+				return (0);
+			break;
+		case BANS_OPER_LTE:
+			AZ(arg1);
+			assert(! isnan(darg1));
+			if (!(darg1 <= darg2))
+				return (0);
+			break;
 		default:
 			WRONG("Wrong BAN_OPER code");
 		}
@@ -668,46 +754,67 @@ ccf_ban(struct cli *cli, const char * const *av, void *priv)
 	}
 }
 
+#define Ms 60
+#define Hs (Ms * 60)
+#define Ds (Hs * 24)
+#define Ws (Ds * 7)
+#define Ys (Ds * 365)
+
+#define Xfmt(buf, var, s, unit)						\
+	((var) >= s && (var) % s == 0)					\
+		bprintf((buf), "%ju" unit, (var) / s)
+
+// XXX move to VTIM?
+#define vdur_render(buf, dur) do {					\
+	uint64_t dec = (uint64_t)floor(dur);				\
+	uint64_t frac = (uint64_t)floor((dur) * 1e3) % UINT64_C(1000);	\
+	if (dec == 0 && frac == 0)					\
+		(void) strncpy(buf, "0s", sizeof(buf));			\
+	else if (dec == 0)						\
+		bprintf((buf), "%jums", frac);				\
+	else if (frac != 0)						\
+		bprintf((buf), "%ju.%03jus", dec, frac);		\
+	else if Xfmt(buf, dec, Ys, "y");				\
+	else if Xfmt(buf, dec, Ws, "w");				\
+	else if Xfmt(buf, dec, Ds, "d");				\
+	else if Xfmt(buf, dec, Hs, "h");				\
+	else if Xfmt(buf, dec, Ms, "m");				\
+	else								\
+		bprintf((buf), "%jus", dec);				\
+	} while (0)
+
 static void
 ban_render(struct cli *cli, const uint8_t *bs, int quote)
 {
 	struct ban_test bt;
 	const uint8_t *be;
+	char buf[64];
 
 	be = bs + ban_len(bs);
 	bs += BANS_HEAD_LEN;
 	while (bs < be) {
 		ban_iter(&bs, &bt);
-		switch (bt.arg1) {
-		case BANS_ARG_URL:
-			VCLI_Out(cli, "req.url");
-			break;
-		case BANS_ARG_REQHTTP:
-			VCLI_Out(cli, "req.http.%.*s",
-			    bt.arg1_spec[0] - 1, bt.arg1_spec + 1);
-			break;
-		case BANS_ARG_OBJHTTP:
-			VCLI_Out(cli, "obj.http.%.*s",
+		ASSERT_BAN_ARG(bt.arg1);
+		ASSERT_BAN_OPER(bt.oper);
+
+		if (BANS_HAS_ARG1_SPEC(bt.arg1))
+			VCLI_Out(cli, "%s.%.*s",
+			    arg_name[BAN_ARGIDX(bt.arg1)],
 			    bt.arg1_spec[0] - 1, bt.arg1_spec + 1);
-			break;
-		case BANS_ARG_OBJSTATUS:
-			VCLI_Out(cli, "obj.status");
-			break;
-		default:
-			WRONG("Wrong BANS_ARG");
-		}
-		switch (bt.oper) {
-		case BANS_OPER_EQ:	VCLI_Out(cli, " == "); break;
-		case BANS_OPER_NEQ:	VCLI_Out(cli, " != "); break;
-		case BANS_OPER_MATCH:	VCLI_Out(cli, " ~ "); break;
-		case BANS_OPER_NMATCH:	VCLI_Out(cli, " !~ "); break;
-		default:
-			WRONG("Wrong BANS_OPER");
-		}
-		if (quote)
-			VCLI_Quote(cli, bt.arg2);
 		else
+			VCLI_Out(cli, "%s", arg_name[BAN_ARGIDX(bt.arg1)]);
+
+		VCLI_Out(cli, " %s ", oper[BAN_OPERIDX(bt.oper)]);
+
+		if (BANS_HAS_ARG2_DOUBLE(bt.arg1)) {
+			vdur_render(buf, bt.arg2_double);
+			VCLI_Out(cli, "%s", buf);
+		} else if (quote) {
+			VCLI_Quote(cli, bt.arg2);
+		} else {
 			VCLI_Out(cli, "%s", bt.arg2);
+		}
+
 		if (bs < be)
 			VCLI_Out(cli, " && ");
 	}
@@ -867,6 +974,7 @@ BAN_Init(void)
 {
 	struct ban_proto *bp;
 
+	BAN_Build_Init();
 	Lck_New(&ban_mtx, lck_ban);
 	CLI_AddFuncs(ban_cmds);
 
@@ -907,4 +1015,6 @@ BAN_Shutdown(void)
 	/* Export the ban list to compact it */
 	ban_export();
 	Lck_Unlock(&ban_mtx);
+
+	BAN_Build_Fini();
 }
diff --git a/bin/varnishd/cache/cache_ban.h b/bin/varnishd/cache/cache_ban.h
index 1a9420825..c4d04fa23 100644
--- a/bin/varnishd/cache/cache_ban.h
+++ b/bin/varnishd/cache/cache_ban.h
@@ -73,16 +73,53 @@
 #define BANS_FLAG_OBJ		(1<<1)
 #define BANS_FLAG_COMPLETED	(1<<2)
 #define BANS_FLAG_HTTP		(1<<3)
+#define BANS_FLAG_DURATION	(1<<4)
+#define BANS_FLAG_NODEDUP	(1<<5)
 
 #define BANS_OPER_EQ		0x10
+#define _BANS_OPER_OFF		BANS_OPER_EQ
 #define BANS_OPER_NEQ		0x11
 #define BANS_OPER_MATCH		0x12
 #define BANS_OPER_NMATCH	0x13
+#define BANS_OPER_GT		0x14
+#define BANS_OPER_GTE		0x15
+#define BANS_OPER_LT		0x16
+#define BANS_OPER_LTE		0x17
+#define _BANS_OPER_LIM		(BANS_OPER_LTE + 1)
+
+#define BAN_OPERIDX(x) ((x) - _BANS_OPER_OFF)
+#define BAN_OPERARRSZ  (_BANS_OPER_LIM - _BANS_OPER_OFF)
+#define ASSERT_BAN_OPER(x) assert((x) >= _BANS_OPER_OFF && (x) < _BANS_OPER_LIM)
 
 #define BANS_ARG_URL		0x18
+#define _BANS_ARG_OFF		BANS_ARG_URL
 #define BANS_ARG_REQHTTP	0x19
 #define BANS_ARG_OBJHTTP	0x1a
 #define BANS_ARG_OBJSTATUS	0x1b
+#define BANS_ARG_OBJTTL	0x1c
+#define BANS_ARG_OBJAGE	0x1d
+#define BANS_ARG_OBJGRACE	0x1e
+#define BANS_ARG_OBJKEEP	0x1f
+#define _BANS_ARG_LIM		(BANS_ARG_OBJKEEP + 1)
+
+#define BAN_ARGIDX(x) ((x) - _BANS_ARG_OFF)
+#define BAN_ARGARRSZ  (_BANS_ARG_LIM - _BANS_ARG_OFF)
+#define ASSERT_BAN_ARG(x) assert((x) >= _BANS_ARG_OFF && (x) < _BANS_ARG_LIM)
+
+// has an arg1_spec (BANS_FLAG_HTTP at build time)
+#define BANS_HAS_ARG1_SPEC(arg)	\
+	((arg) == BANS_ARG_REQHTTP ||	\
+	 (arg) == BANS_ARG_OBJHTTP)
+
+// has an arg2_spec (regex)
+#define BANS_HAS_ARG2_SPEC(oper)	\
+	((oper) == BANS_OPER_MATCH ||	\
+	 (oper) == BANS_OPER_NMATCH)
+
+// has an arg2_double (BANS_FLAG_DURATION at build time)
+#define BANS_HAS_ARG2_DOUBLE(arg)	\
+	((arg) >= BANS_ARG_OBJTTL &&	\
+	 (arg) <= BANS_ARG_OBJKEEP)
 
 /*--------------------------------------------------------------------*/
 
diff --git a/bin/varnishd/cache/cache_ban_build.c b/bin/varnishd/cache/cache_ban_build.c
index e3b9873a8..948df5d8a 100644
--- a/bin/varnishd/cache/cache_ban_build.c
+++ b/bin/varnishd/cache/cache_ban_build.c
@@ -37,6 +37,10 @@
 
 #include "vend.h"
 #include "vtim.h"
+#include "vnum.h"
+
+void BAN_Build_Init(void);
+void BAN_Build_Fini(void);
 
 struct ban_proto {
 	unsigned		magic;
@@ -61,6 +65,24 @@ static const struct pvar {
 	{ 0, 0, 0}
 };
 
+/* operators allowed per argument (pvar.tag) */
+static const unsigned arg_opervalid[BAN_ARGARRSZ + 1] = {
+#define ARGOPER(arg, mask) [BAN_ARGIDX(arg)] = (mask),
+#include "tbl/ban_arg_oper.h"
+	[BAN_ARGARRSZ] = 0
+};
+
+// init'ed in _Init
+static const char *arg_operhelp[BAN_ARGARRSZ + 1];
+
+// operators
+const char * const oper[BAN_OPERARRSZ + 1] = {
+#define OPER(op, str) [BAN_OPERIDX(op)] = (str),
+#include "tbl/ban_oper.h"
+	[BAN_OPERARRSZ] = NULL
+};
+
+
 /*--------------------------------------------------------------------
  */
 
@@ -177,6 +199,18 @@ ban_parse_regexp(struct ban_proto *bp, const char *a3)
 	return (0);
 }
 
+static int
+ban_parse_oper(const char *p)
+{
+	int i;
+
+	for (i = 0; i < BAN_OPERARRSZ; i++) {
+		if (!strcmp(p, oper[i]))
+			return _BANS_OPER_OFF + i;
+	}
+	return -1;
+}
+
 /*--------------------------------------------------------------------
  * Add a (and'ed) test-condition to a ban
  */
@@ -186,7 +220,10 @@ BAN_AddTest(struct ban_proto *bp,
     const char *a1, const char *a2, const char *a3)
 {
 	const struct pvar *pv;
-	const char *err;
+	double darg;
+	uint64_t dtmp;
+	uint8_t denc[sizeof darg];
+	int op;
 
 	CHECK_OBJ_NOTNULL(bp, BAN_PROTO_MAGIC);
 	AN(bp->vsb);
@@ -208,28 +245,46 @@ BAN_AddTest(struct ban_proto *bp,
 	bp->flags |= pv->flag;
 
 	VSB_putc(bp->vsb, pv->tag);
-	if (pv->flag & BANS_FLAG_HTTP)
+	if (pv->flag & BANS_FLAG_HTTP) {
+		assert(BANS_HAS_ARG1_SPEC(pv->tag));
 		ban_parse_http(bp, a1 + strlen(pv->name));
+	}
+
+	op = ban_parse_oper(a2);
+	if (op < 0 ||
+	    ((1<<BAN_OPERIDX(op)) & arg_opervalid[BAN_ARGIDX(pv->tag)]) == 0)
+		return (ban_error(bp,
+				  "expected conditional (%s) got \"%s\"",
+				  arg_operhelp[BAN_ARGIDX(pv->tag)],
+				  a2));
+
+	if ((pv->flag & BANS_FLAG_DURATION) == 0) {
+		assert(! BANS_HAS_ARG2_DOUBLE(pv->tag));
 
-	ban_add_lump(bp, a3, strlen(a3) + 1);
-	if (!strcmp(a2, "~")) {
-		VSB_putc(bp->vsb, BANS_OPER_MATCH);
-		err = ban_parse_regexp(bp, a3);
-		if (err)
-			return (err);
-	} else if (!strcmp(a2, "!~")) {
-		VSB_putc(bp->vsb, BANS_OPER_NMATCH);
-		err = ban_parse_regexp(bp, a3);
-		if (err)
-			return (err);
-	} else if (!strcmp(a2, "==")) {
-		VSB_putc(bp->vsb, BANS_OPER_EQ);
-	} else if (!strcmp(a2, "!=")) {
-		VSB_putc(bp->vsb, BANS_OPER_NEQ);
-	} else {
+		ban_add_lump(bp, a3, strlen(a3) + 1);
+		VSB_putc(bp->vsb, op);
+
+		if (! BANS_HAS_ARG2_SPEC(op))
+			return (NULL);
+
+		return (ban_parse_regexp(bp, a3));
+	}
+
+	assert(pv->flag & BANS_FLAG_DURATION);
+	assert(BANS_HAS_ARG2_DOUBLE(pv->tag));
+	darg = VNUM_duration(a3);
+	if (isnan(darg)) {
 		return (ban_error(bp,
-		    "expected conditional (~, !~, == or !=) got \"%s\"", a2));
+		    "expected duration <n.nn>[ms|s|m|h|d|w|y] got \"%s\"", a3));
 	}
+
+	assert(sizeof darg == sizeof dtmp);
+	assert(sizeof dtmp == sizeof denc);
+	memcpy(&dtmp, &darg, sizeof dtmp);
+	vbe64enc(denc, dtmp);
+
+	ban_add_lump(bp, denc, sizeof denc);
+	VSB_putc(bp->vsb, op);
 	return (NULL);
 }
 
@@ -275,6 +330,7 @@ BAN_Commit(struct ban_proto *bp)
 
 	b->flags = bp->flags;
 
+	// XXX why don't we vbe*enc timestamp and flags?
 	memset(b->spec, 0, BANS_HEAD_LEN);
 	t0 = VTIM_real();
 	memcpy(b->spec + BANS_TIMESTAMP, &t0, sizeof t0);
@@ -329,3 +385,66 @@ BAN_Commit(struct ban_proto *bp)
 	BAN_Abandon(bp);
 	return (NULL);
 }
+
+static void
+ban_build_arg_operhelp(struct vsb *vsb, int arg)
+{
+	unsigned mask;
+	const char *p = NULL, *n = NULL;
+	int i;
+
+	ASSERT_BAN_ARG(arg);
+	mask = arg_opervalid[BAN_ARGIDX(arg)];
+
+	for (i = 0; i < BAN_OPERARRSZ; i++) {
+		if ((mask & (1<<i)) == 0)
+			continue;
+		if (p == NULL)
+			p = oper[i];
+		else if (n == NULL)
+			n = oper[i];
+		else {
+			VSB_cat(vsb, p);
+			VSB_cat(vsb, ", ");
+			p = n;
+			n = oper[i];
+		}
+	}
+
+	if (n) {
+		AN(p);
+		VSB_cat(vsb, p);
+		VSB_cat(vsb, " or ");
+		VSB_cat(vsb, n);
+		return;
+	}
+
+	AN(p);
+	VSB_cat(vsb, p);
+}
+
+void
+BAN_Build_Init(void) {
+	struct vsb *vsb = VSB_new_auto();
+	int i;
+
+	for (i = _BANS_ARG_OFF; i < _BANS_ARG_LIM; i ++) {
+		VSB_clear(vsb);
+		ban_build_arg_operhelp(vsb, i);
+		AZ(VSB_finish(vsb));
+
+		arg_operhelp[BAN_ARGIDX(i)] = strdup(VSB_data(vsb));
+		AN(arg_operhelp[BAN_ARGIDX(i)]);
+	}
+	arg_operhelp[BAN_ARGIDX(i)] = NULL;
+	VSB_destroy(&vsb);
+	AZ(vsb);
+}
+
+void
+BAN_Build_Fini(void) {
+	int i;
+
+	for (i = 0; i < BAN_ARGARRSZ; i++)
+		free(TRUST_ME(arg_operhelp[i]));
+}
diff --git a/bin/varnishtest/tests/c00059.vtc b/bin/varnishtest/tests/c00059.vtc
index 156bff35c..410e794a4 100644
--- a/bin/varnishtest/tests/c00059.vtc
+++ b/bin/varnishtest/tests/c00059.vtc
@@ -1,10 +1,20 @@
-varnishtest "test ban obj.status"
+varnishtest "test ban obj.* except obj.http.*"
+
+# see c00021.vtc for obj.http.* tests
 
 server s1 {
 	rxreq
-	txresp -bodylen 5
+	txresp -bodylen 1
+	rxreq
+	txresp -bodylen 2
 	rxreq
 	txresp -bodylen 3
+	rxreq
+	txresp -bodylen 4
+	rxreq
+	txresp -bodylen 5
+	rxreq
+	txresp -bodylen 6
 } -start
 
 varnish v1 -vcl+backend {} -start
@@ -12,16 +22,131 @@ varnish v1 -vcl+backend {} -start
 client c1 {
 	txreq
 	rxresp
-	expect resp.bodylen == 5
+	expect resp.bodylen == 1
 } -run
 
 varnish v1 -cliok "ban obj.status == 201"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 1
+} -run
+
 varnish v1 -cliok "ban obj.status == 200"
 varnish v1 -cliok "ban.list"
 varnish v1 -clijson "ban.list -j"
 
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 2
+} -run
+
+varnish v1 -cliok "ban obj.keep == 0s && obj.ttl > 1d"
+varnish v1 -cliok "ban obj.keep == 0s && obj.ttl > 1d"
+
+# BANS_FLAG_NODEDUP
+varnish v1 -cliexpect {(?s) obj\.ttl > 1d\b.*obj\.ttl > 1d\b} "ban.list"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 2
+} -run
+
+varnish v1 -cliok "ban obj.ttl <= 2m"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 3
+} -run
+
+varnish v1 -cliok "ban obj.age > 1d"
+varnish v1 -cliok "ban obj.age > 1d"
+
+# BANS_FLAG_NODEDUP
+varnish v1 -cliexpect {(?s) obj\.age > 1d\b.*obj\.age > 1d\b} "ban.list"
+
 client c1 {
 	txreq
 	rxresp
 	expect resp.bodylen == 3
 } -run
+
+varnish v1 -cliok "ban obj.age < 1m"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 4
+} -run
+
+varnish v1 -cliok "ban obj.grace != 10s"
+varnish v1 -cliok "ban obj.grace != 10s"
+
+# ! BANS_FLAG_NODEDUP
+varnish v1 -cliexpect {(?s) obj\.grace != 10s\b.* \d C\b} "ban.list"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 4
+} -run
+
+varnish v1 -cliok "ban obj.grace == 10s"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 5
+} -run
+
+varnish v1 -cliok "ban obj.keep != 0s"
+varnish v1 -cliok "ban obj.keep != 0s"
+
+# ! BANS_FLAG_NODEDUP
+varnish v1 -cliexpect {(?s) obj\.keep != 0s\b.* \d C\b} "ban.list"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 5
+} -run
+
+varnish v1 -cliok "ban obj.keep == 0s"
+
+client c1 {
+	txreq
+	rxresp
+	expect resp.bodylen == 6
+} -run
+
+# duration formatting - 0s is being tested above
+varnish v1 -cliok "ban obj.keep == 123ms"
+varnish v1 -cliexpect {(?s) obj\.keep == 123ms\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 0.456s"
+varnish v1 -cliexpect {(?s) obj\.keep == 456ms\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 6.789s"
+varnish v1 -cliexpect {(?s) obj\.keep == 6.789s\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 42y"
+varnish v1 -cliexpect {(?s) obj\.keep == 42y\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 365d"
+varnish v1 -cliexpect {(?s) obj\.keep == 1y\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 9w"
+varnish v1 -cliexpect {(?s) obj\.keep == 9w\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 7d"
+varnish v1 -cliexpect {(?s) obj\.keep == 1w\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 3d"
+varnish v1 -cliexpect {(?s) obj\.keep == 3d\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 24h"
+varnish v1 -cliexpect {(?s) obj\.keep == 1d\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 18h"
+varnish v1 -cliexpect {(?s) obj\.keep == 18h\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 1.5h"
+varnish v1 -cliexpect {(?s) obj\.keep == 90m\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 10m"
+varnish v1 -cliexpect {(?s) obj\.keep == 10m\b} "ban.list"
+varnish v1 -cliok "ban obj.keep == 0.5m"
+varnish v1 -cliexpect {(?s) obj\.keep == 30s\b} "ban.list"
diff --git a/bin/varnishtest/tests/v00011.vtc b/bin/varnishtest/tests/v00011.vtc
index 9d391d3c0..8a613be33 100644
--- a/bin/varnishtest/tests/v00011.vtc
+++ b/bin/varnishtest/tests/v00011.vtc
@@ -1,4 +1,4 @@
-varnishtest "Test vcl purging"
+varnishtest "Test vcl ban()"
 
 server s1 {
 	rxreq
@@ -18,6 +18,9 @@ varnish v1 -vcl+backend {
 			ban("req.url // bar");
 			ban("req.url == bar //");
 			ban("foo == bar //");
+			ban("obj.age == 4");
+			ban("obj.age // 4d");
+			ban("obj.http.foo > 4d");
 			ban("req.url ~ ^/$");
 			return (synth(209,"foo"));
 		}
@@ -36,9 +39,13 @@ logexpect l1 -v v1 -d 1 -g vxid {
 	expect * 1004	VCL_Error {ban[(][)]: No ban conditions found[.]}
 	expect * 1004	VCL_Error {ban[(][)]: Expected comparison operator[.]}
 	expect * 1004	VCL_Error {ban[(][)]: Expected second operand[.]}
-	expect * 1004	VCL_Error {ban[(][)]: expected conditional [(]~, !~, == or !=[)] got "//"}
-	expect * 1004	VCL_Error {ban[(][)]: Expected && between conditions, found .//.}
-	expect * 1004	VCL_Error {ban[(][)]: Unknown or unsupported field .foo.}
+	expect * 1004	VCL_Error {ban[(][)]: expected conditional [(]==, !=, ~ or !~[)] got "//"}
+	expect * 1004	VCL_Error {ban[(][)]: Expected && between conditions, found "//"}
+	expect * 1004	VCL_Error {ban[(][)]: Unknown or unsupported field "foo"}
+	expect * 1004	VCL_Error {ban[(][)]: expected duration <n.nn>.ms|s|m|h|d|w|y. got "4"}
+	expect * 1004	VCL_Error {ban[(][)]: expected conditional [(]==, !=, >, >=, < or <=[)] got "//"}
+	expect * 1004	VCL_Error {ban[(][)]: expected conditional [(]==, !=, ~ or !~[)] got ">"}
+
 
 } -start
 
diff --git a/doc/sphinx/reference/vcl.rst b/doc/sphinx/reference/vcl.rst
index c1de845e7..03382272a 100644
--- a/doc/sphinx/reference/vcl.rst
+++ b/doc/sphinx/reference/vcl.rst
@@ -457,23 +457,59 @@ ban(STRING)
 
   * *<field>*:
 
-    * ``req.url``: The request url
-    * ``req.http.*``: Any request header
-    * ``obj.status``: The cache object status
-    * ``obj.http.*``: Any cache object header
+    * string fields:
+
+      * ``req.url``: The request url
+      * ``req.http.*``: Any request header
+      * ``obj.status``: The cache object status
+      * ``obj.http.*``: Any cache object header
+
+      ``obj.status`` is treated as a string despite the fact that it
+      is actually an integer.
+
+    * duration fields:
+
+      * ``obj.ttl``: Remaining ttl at the time the ban is issued
+      * ``obj.age``: Object age at the time the ban is issued
+      * ``obj.grace``: The grace time of the object
+      * ``obj.keep``: The keep time of the object
+
 
   * *<operator>*:
 
-    * ``==``: *<field>* and *<arg>* are equal strings (case sensitive)
-    * ``!=``: *<field>* and *<arg>* are unequal strings (case sensitive)
-    * ``~``: *<field>* matches the regular expression *<arg>*
-    * ``!~``:*<field>* does not match the regular expression *<arg>*
+    * for all fields:
+
+      * ``==``: *<field>* and *<arg>* are equal
+      * ``!=``: *<field>* and *<arg>* are unequal
+
+      strings are compared case sensitively
+
+    * for string fields:
+
+      * ``~``: *<field>* matches the regular expression *<arg>*
+      * ``!~``:*<field>* does not match the regular expression *<arg>*
+
+    * for duration fields:
+
+      * ``>``: *<field>* is greater than *<arg>*
+      * ``>=``: *<field>* is greater than or equal to *<arg>*
+      * ``<``: *<field>* is less than *<arg>*
+      * ``<=``: *<field>* is less than or equal to *<arg>*
+
+
+  * *<arg>*:
+
+    * for string fields:
+
+      Either a literal string or a regular expression. Note that
+      *<arg>* does not use any of the string delimiters like ``"`` or
+      ``{"``\ *...*\ ``"}`` used elsewhere in varnish. To match
+      against strings containing whitespace, regular expressions
+      containing ``\s`` can be used.
+
+    * for duration fields:
 
-  * *<arg>*: Either a literal string or a regular expression. Note
-    that *<arg>* does not use any of the string delimiters like ``"``
-    or ``{"``\ *...*\ ``"}`` used elsewhere in varnish. To match
-    against strings containing whitespace, regular expressions
-    containing ``\s`` can be used.
+      A VCL duration like ``10s``, ``5m`` or ``1h``, see `Durations`_
 
   Expressions can be chained using the *and* operator ``&&``. For *or*
   semantics, use several bans.
diff --git a/doc/sphinx/users-guide/purging.rst b/doc/sphinx/users-guide/purging.rst
index afe1fe266..58fa52e45 100644
--- a/doc/sphinx/users-guide/purging.rst
+++ b/doc/sphinx/users-guide/purging.rst
@@ -149,9 +149,12 @@ will produce a status of all current bans::
 The ban list contains the ID of the ban, the timestamp when the ban
 entered the ban list. A count of the objects that has reached this point
 in the ban list, optionally postfixed with a 'G' for "Gone", if the ban
-is no longer valid.  Finally, the ban expression is listed. The ban can
-be marked as "Gone" if it is a duplicate ban, but is still kept in the list
-for optimization purposes.
+is no longer valid.  Finally, the ban expression is listed. Notice
+that durations are not necessarily expressed in the originally given
+unit, for instance ``7d`` will get turned into ``1w``.
+
+The ban can be marked as "Gone" if it is a duplicate ban, but is still
+kept in the list for optimization purposes.
 
 Forcing a cache miss
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/include/Makefile.am b/include/Makefile.am
index 63757e48c..ddd5fefcf 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -5,6 +5,8 @@ nobase_pkginclude_HEADERS = \
 	tbl/acct_fields_bereq.h \
 	tbl/acct_fields_req.h \
 	tbl/backend_poll.h \
+	tbl/ban_arg_oper.h \
+	tbl/ban_oper.h \
 	tbl/ban_vars.h \
 	tbl/bo_flags.h \
 	tbl/boc_state.h \
diff --git a/include/tbl/ban_arg_oper.h b/include/tbl/ban_arg_oper.h
new file mode 100644
index 000000000..df372c2aa
--- /dev/null
+++ b/include/tbl/ban_arg_oper.h
@@ -0,0 +1,58 @@
+/*-
+ * Copyright 2017 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Author: Nils Goroll <nils.goroll at uplex.de>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Define which operators are valid for which ban arguments
+ */
+
+/*lint -save -e525 -e539 */
+
+#define BANS_OPER_STRING			\
+	(1<<BAN_OPERIDX(BANS_OPER_EQ) |	\
+	 1<<BAN_OPERIDX(BANS_OPER_NEQ) |	\
+	 1<<BAN_OPERIDX(BANS_OPER_MATCH) |	\
+	 1<<BAN_OPERIDX(BANS_OPER_NMATCH))
+
+#define BANS_OPER_DURATION			\
+	(1<<BAN_OPERIDX(BANS_OPER_EQ) |		\
+	 1<<BAN_OPERIDX(BANS_OPER_NEQ) |	\
+	 1<<BAN_OPERIDX(BANS_OPER_GT) |		\
+	 1<<BAN_OPERIDX(BANS_OPER_GTE) |	\
+	 1<<BAN_OPERIDX(BANS_OPER_LT) |		\
+	 1<<BAN_OPERIDX(BANS_OPER_LTE))
+
+ARGOPER(BANS_ARG_URL,		BANS_OPER_STRING)
+ARGOPER(BANS_ARG_REQHTTP,	BANS_OPER_STRING)
+ARGOPER(BANS_ARG_OBJHTTP,	BANS_OPER_STRING)	// INT?
+ARGOPER(BANS_ARG_OBJSTATUS,	BANS_OPER_STRING)
+ARGOPER(BANS_ARG_OBJTTL,	BANS_OPER_DURATION)
+ARGOPER(BANS_ARG_OBJAGE,	BANS_OPER_DURATION)
+ARGOPER(BANS_ARG_OBJGRACE,	BANS_OPER_DURATION)
+ARGOPER(BANS_ARG_OBJKEEP,	BANS_OPER_DURATION)
+
+#undef ARGOPER
+#undef BANS_OPER_STRING
+#undef BANS_OPER_DURATION
+
+/*lint -restore */
diff --git a/include/tbl/ban_oper.h b/include/tbl/ban_oper.h
new file mode 100644
index 000000000..06430c1f3
--- /dev/null
+++ b/include/tbl/ban_oper.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright 2017 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Author: Nils Goroll <nils.goroll at uplex.de>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Define ban operators
+ */
+
+/*lint -save -e525 -e539 */
+
+OPER(BANS_OPER_EQ,	"==")
+OPER(BANS_OPER_NEQ,	"!=")
+OPER(BANS_OPER_MATCH,	"~")
+OPER(BANS_OPER_NMATCH,	"!~")
+OPER(BANS_OPER_GT,	">")
+OPER(BANS_OPER_GTE,	">=")
+OPER(BANS_OPER_LT,	"<")
+OPER(BANS_OPER_LTE,	"<=")
+
+#undef OPER
+
+/*lint -restore */
diff --git a/include/tbl/ban_vars.h b/include/tbl/ban_vars.h
index 14e06b010..db7ff2ef7 100644
--- a/include/tbl/ban_vars.h
+++ b/include/tbl/ban_vars.h
@@ -31,10 +31,30 @@
 
 /*lint -save -e525 -e539 */
 
-PVAR("req.url",		BANS_FLAG_REQ,			BANS_ARG_URL)
-PVAR("req.http.",	BANS_FLAG_REQ | BANS_FLAG_HTTP,	BANS_ARG_REQHTTP)
-PVAR("obj.status",	BANS_FLAG_OBJ,			BANS_ARG_OBJSTATUS)
-PVAR("obj.http.",	BANS_FLAG_OBJ | BANS_FLAG_HTTP,	BANS_ARG_OBJHTTP)
+PVAR("req.url",
+     BANS_FLAG_REQ,
+     BANS_ARG_URL)
+PVAR("req.http.",
+     BANS_FLAG_REQ | BANS_FLAG_HTTP,
+     BANS_ARG_REQHTTP)
+PVAR("obj.status",
+     BANS_FLAG_OBJ,
+     BANS_ARG_OBJSTATUS)
+PVAR("obj.http.",
+     BANS_FLAG_OBJ | BANS_FLAG_HTTP,
+     BANS_ARG_OBJHTTP)
+PVAR("obj.ttl",
+     BANS_FLAG_OBJ | BANS_FLAG_DURATION | BANS_FLAG_NODEDUP,
+     BANS_ARG_OBJTTL)
+PVAR("obj.age",
+     BANS_FLAG_OBJ | BANS_FLAG_DURATION | BANS_FLAG_NODEDUP,
+     BANS_ARG_OBJAGE)
+PVAR("obj.grace",
+     BANS_FLAG_OBJ | BANS_FLAG_DURATION,
+     BANS_ARG_OBJGRACE)
+PVAR("obj.keep",
+     BANS_FLAG_OBJ | BANS_FLAG_DURATION,
+     BANS_ARG_OBJKEEP)
 #undef PVAR
 
 /*lint -restore */
diff --git a/include/tbl/cli_cmds.h b/include/tbl/cli_cmds.h
index 5c28ed5c8..91f30b1b8 100644
--- a/include/tbl/cli_cmds.h
+++ b/include/tbl/cli_cmds.h
@@ -61,7 +61,9 @@ CLI_CMD(BAN_LIST,
 	"    * ``R`` for req.* tests\n\n"
 	"    * ``O`` for obj.* tests\n\n"
 	"    * Pointer to ban object\n\n"
-	"  * Ban specification",
+	"  * Ban specification\n\n"
+	"  Durations of ban specifications get normalized, for example \"7d\""
+	" gets changed into \"1w\".",
 
 	0, 0
 )


More information about the varnish-commit mailing list