Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 11 Aug 2014 17:34:26 +0000 (UTC)
From:      Alexander V. Chernikov <melifaro@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r269821 - in projects/ipfw: sbin/ipfw sys/netinet sys/netpfil/ipfw
Message-ID:  <53e8fea2.296d.53afb556@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: melifaro
Date: Mon Aug 11 17:34:25 2014
New Revision: 269821
URL: http://svnweb.freebsd.org/changeset/base/269821

Log:
  * Add support for batched add/delete for ipfw tables
  * Add support for atomic batches add (all or none).
  * Fix panic on deleting non-existing entry in radix algo.
  
  Examples:
  
  # si is empty
  # ipfw table si add 1.1.1.1/32 1111 2.2.2.2/32 2222
  added: 1.1.1.1/32 1111
  added: 2.2.2.2/32 2222
  # ipfw table si add 2.2.2.2/32 2200 4.4.4.4/32 4444
  exists: 2.2.2.2/32 2200
  added: 4.4.4.4/32 4444
  ipfw: Adding record failed: record already exists
  ^^^^^ Returns error but keeps inserted items
  # ipfw table si list
  +++ table(si), set(0) +++
  1.1.1.1/32 1111
  2.2.2.2/32 2222
  4.4.4.4/32 4444
  # ipfw table si atomic add 3.3.3.3/32 3333 4.4.4.4/32 4400 5.5.5.5/32 5555
  added(reverted): 3.3.3.3/32 3333
  exists: 4.4.4.4/32 4400
  ignored: 5.5.5.5/32 5555
  ipfw: Adding record failed: record already exists
  ^^^^^ Returns error and reverts added records
  # ipfw table si list
  +++ table(si), set(0) +++
  1.1.1.1/32 1111
  2.2.2.2/32 2222
  4.4.4.4/32 4444

Modified:
  projects/ipfw/sbin/ipfw/ipfw2.h
  projects/ipfw/sbin/ipfw/tables.c
  projects/ipfw/sys/netinet/ip_fw.h
  projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h
  projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c
  projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c
  projects/ipfw/sys/netpfil/ipfw/ip_fw_table.h
  projects/ipfw/sys/netpfil/ipfw/ip_fw_table_algo.c

Modified: projects/ipfw/sbin/ipfw/ipfw2.h
==============================================================================
--- projects/ipfw/sbin/ipfw/ipfw2.h	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sbin/ipfw/ipfw2.h	Mon Aug 11 17:34:25 2014	(r269821)
@@ -224,6 +224,7 @@ enum tokens {
 	TOK_ALGO,
 	TOK_TALIST,
 	TOK_FTYPE,
+	TOK_ATOMIC,
 };
 /*
  * the following macro returns an error message if we run out of

Modified: projects/ipfw/sbin/ipfw/tables.c
==============================================================================
--- projects/ipfw/sbin/ipfw/tables.c	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sbin/ipfw/tables.c	Mon Aug 11 17:34:25 2014	(r269821)
@@ -50,7 +50,7 @@
 
 static void table_list(ipfw_xtable_info *i, int need_header);
 static void table_modify_record(ipfw_obj_header *oh, int ac, char *av[],
-    int add, int update);
+    int add, int quiet, int update, int atomic);
 static int table_flush(ipfw_obj_header *oh);
 static int table_destroy(ipfw_obj_header *oh);
 static int table_do_create(ipfw_obj_header *oh, ipfw_xtable_info *i);
@@ -114,6 +114,7 @@ static struct _s_x tablecmds[] = {
       { "detail",	TOK_DETAIL },
       { "list",		TOK_LIST },
       { "lookup",	TOK_LOOKUP },
+      { "atomic",	TOK_ATOMIC },
       { NULL, 0 }
 };
 
@@ -144,7 +145,7 @@ void
 ipfw_table_handler(int ac, char *av[])
 {
 	int do_add, is_all;
-	int error, tcmd;
+	int atomic, error, tcmd;
 	ipfw_xtable_info i;
 	ipfw_obj_header oh;
 	char *tablename;
@@ -176,6 +177,21 @@ ipfw_table_handler(int ac, char *av[])
 
 	if ((tcmd = match_token(tablecmds, *av)) == -1)
 		errx(EX_USAGE, "invalid table command %s", *av);
+	/* Check if atomic operation was requested */
+	atomic = 0;
+	if (tcmd == TOK_ATOMIC) {
+		ac--; av++;
+		NEED1("atomic needs command");
+		if ((tcmd = match_token(tablecmds, *av)) == -1)
+			errx(EX_USAGE, "invalid table command %s", *av);
+		switch (tcmd) {
+		case TOK_ADD:
+			break;
+		default:
+			errx(EX_USAGE, "atomic is not compatible with %s", *av);
+		}
+		atomic = 1;
+	}
 
 	switch (tcmd) {
 	case TOK_LIST:
@@ -193,7 +209,8 @@ ipfw_table_handler(int ac, char *av[])
 	case TOK_DEL:
 		do_add = **av == 'a';
 		ac--; av++;
-		table_modify_record(&oh, ac, av, do_add, co.do_quiet);
+		table_modify_record(&oh, ac, av, do_add, co.do_quiet,
+		    co.do_quiet, atomic);
 		break;
 	case TOK_CREATE:
 		ac--; av++;
@@ -785,76 +802,195 @@ table_flush_one(ipfw_xtable_info *i, voi
 
 static int
 table_do_modify_record(int cmd, ipfw_obj_header *oh,
-    ipfw_obj_tentry *tent, int update)
+    ipfw_obj_tentry *tent, int count, int atomic)
 {
 	ipfw_obj_ctlv *ctlv;
+	ipfw_obj_tentry *tent_base;
+	caddr_t pbuf;
 	char xbuf[sizeof(*oh) + sizeof(ipfw_obj_ctlv) + sizeof(*tent)];
-	int error;
+	int error, i;
+	size_t sz;
 
-	memset(xbuf, 0, sizeof(xbuf));
-	memcpy(xbuf, oh, sizeof(*oh));
-	oh = (ipfw_obj_header *)xbuf;
+	sz = sizeof(*ctlv) + sizeof(*tent) * count;
+	if (count == 1) {
+		memset(xbuf, 0, sizeof(xbuf));
+		pbuf = xbuf;
+	} else {
+		if ((pbuf = calloc(1, sizeof(*oh) + sz)) == NULL)
+			return (ENOMEM);
+	}
+
+	memcpy(pbuf, oh, sizeof(*oh));
+	oh = (ipfw_obj_header *)pbuf;
 	oh->opheader.version = 1;
 
 	ctlv = (ipfw_obj_ctlv *)(oh + 1);
-	ctlv->count = 1;
-	ctlv->head.length = sizeof(*ctlv) + sizeof(*tent);
+	ctlv->count = count;
+	ctlv->head.length = sz;
+	if (atomic != 0)
+		ctlv->flags |= IPFW_CTF_ATOMIC;
+
+	tent_base = tent;
+	memcpy(ctlv + 1, tent, sizeof(*tent) * count);
+	tent = (ipfw_obj_tentry *)(ctlv + 1);
+	for (i = 0; i < count; i++, tent++) {
+		tent->head.length = sizeof(ipfw_obj_tentry);
+		tent->idx = oh->idx;
+	}
 
-	memcpy(ctlv + 1, tent, sizeof(*tent));
+	sz += sizeof(*oh);
+	error = do_get3(cmd, &oh->opheader, &sz);
 	tent = (ipfw_obj_tentry *)(ctlv + 1);
-	if (update != 0)
-		tent->head.flags |= IPFW_TF_UPDATE;
-	tent->head.length = sizeof(ipfw_obj_tentry);
+	/* Copy result back to provided buffer */
+	memcpy(tent_base, ctlv + 1, sizeof(*tent) * count);
 
-	error = do_set3(cmd, &oh->opheader, sizeof(xbuf));
+	if (pbuf != xbuf)
+		free(pbuf);
 
 	return (error);
 }
 
 static void
-table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, int update)
+table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add,
+    int quiet, int update, int atomic)
 {
-	ipfw_obj_tentry tent;
+	ipfw_obj_tentry *ptent, tent, *tent_buf;
 	ipfw_xtable_info xi;
 	uint8_t type, vtype;
-	int cmd, error;
-	char *texterr, *etxt;
+	int cmd, count, error, i, ignored;
+	char *texterr, *etxt, *px;
 
 	if (ac == 0)
 		errx(EX_USAGE, "address required");
 	
-	memset(&tent, 0, sizeof(tent));
-	tent.head.length = sizeof(tent);
-	tent.idx = 1;
+	if (add != 0) {
+		cmd = IP_FW_TABLE_XADD;
+		texterr = "Adding record failed";
+	} else {
+		cmd = IP_FW_TABLE_XDEL;
+		texterr = "Deleting record failed";
+	}
+
+	/*
+	 * Calculate number of entries:
+	 * Assume [key val] x N for add
+	 * and
+	 * key x N for delete
+	 */
+	count = (add != 0) ? ac / 2 + 1 : ac;
+
+	if (count <= 1) {
+		/* Adding single entry with/without value */
+		memset(&tent, 0, sizeof(tent));
+		tent_buf = &tent;
+	} else {
+		
+		if ((tent_buf = calloc(count, sizeof(tent))) == NULL)
+			errx(EX_OSERR,
+			    "Unable to allocate memory for all entries");
+	}
+	ptent = tent_buf;
+
+	memset(&xi, 0, sizeof(xi));
+	count = 0;
+	while (ac > 0) {
+		tentry_fill_key(oh, ptent, *av, &type, &vtype, &xi);
+
+		/*
+		 * compability layer: auto-create table if not exists
+		 */
+		if (xi.tablename[0] == '\0') {
+			xi.type = type;
+			xi.vtype = vtype;
+			strlcpy(xi.tablename, oh->ntlv.name,
+			    sizeof(xi.tablename));
+			fprintf(stderr, "DEPRECATED: inserting data info "
+			    "non-existent table %s. (auto-created)\n",
+			    xi.tablename);
+			table_do_create(oh, &xi);
+		}
+	
+		oh->ntlv.type = type;
+		ac--; av++;
+	
+		if (add != 0 && ac > 0) {
+			tentry_fill_value(oh, ptent, *av, type, vtype);
+			ac--; av++;
+		}
 
-	tentry_fill_key(oh, &tent, *av, &type, &vtype, &xi);
+		if (update != 0)
+			ptent->head.flags |= IPFW_TF_UPDATE;
+
+		count++;
+		ptent++;
+	}
+
+	error = table_do_modify_record(cmd, oh, tent_buf, count, atomic);
 
 	/*
-	 * compability layer: auto-create table if not exists
+	 * Compatibility stuff: do not yell on duplicate keys or
+	 * failed deletions.
 	 */
-	if (xi.tablename[0] == '\0') {
-		xi.type = type;
-		xi.vtype = vtype;
-		strlcpy(xi.tablename, oh->ntlv.name, sizeof(xi.tablename));
-		fprintf(stderr, "DEPRECATED: inserting data info non-existent "
-		    "table %s. (auto-created)\n", xi.tablename);
-		table_do_create(oh, &xi);
+	if (error == 0 || (error == EEXIST && add != 0) ||
+	    (error == ENOENT && add == 0)) {
+		if (quiet != 0) {
+			if (tent_buf != &tent)
+				free(tent_buf);
+			return;
+		}
 	}
 
-	oh->ntlv.type = type;
-	ac--; av++;
+	/* Report results back */
+	ptent = tent_buf;
+	for (i = 0; i < count; ptent++, i++) {
+		ignored = 0;
+		switch (ptent->result) {
+		case IPFW_TR_ADDED:
+			px = "added";
+			break;
+		case IPFW_TR_DELETED:
+			px = "deleted";
+			break;
+		case IPFW_TR_UPDATED:
+			px = "updated";
+			break;
+		case IPFW_TR_LIMIT:
+			px = "limit";
+			ignored = 1;
+			break;
+		case IPFW_TR_ERROR:
+			px = "error";
+			ignored = 1;
+			break;
+		case IPFW_TR_NOTFOUND:
+			px = "notfound";
+			ignored = 1;
+			break;
+		case IPFW_TR_EXISTS:
+			px = "exists";
+			ignored = 1;
+			break;
+		case IPFW_TR_IGNORED:
+			px = "ignored";
+			ignored = 1;
+			break;
+		default:
+			px = "unknown";
+			ignored = 1;
+		}
 
-	if (add != 0) {
-		if (ac > 0)
-			tentry_fill_value(oh, &tent, *av, type, vtype);
-		cmd = IP_FW_TABLE_XADD;
-		texterr = "Adding record failed";
-	} else {
-		cmd = IP_FW_TABLE_XDEL;
-		texterr = "Deleting record failed";
+		if (error != 0 && atomic != 0 && ignored == 0)
+			printf("%s(reverted): ", px);
+		else
+			printf("%s: ", px);
+
+		table_show_entry(&xi, ptent);
 	}
 
-	if ((error = table_do_modify_record(cmd, oh, &tent, update)) == 0)
+	if (tent_buf != &tent)
+		free(tent_buf);
+
+	if (error == 0)
 		return;
 
 	/* Try to provide more human-readable error */
@@ -924,6 +1060,7 @@ table_lookup(ipfw_obj_header *oh, int ac
 
 	strlcpy(key, *av, sizeof(key));
 
+	memset(&xi, 0, sizeof(xi));
 	error = table_do_lookup(oh, key, &xi, &xtent);
 
 	switch (error) {
@@ -1144,7 +1281,10 @@ tentry_fill_key(ipfw_obj_header *oh, ipf
 	tflags = 0;
 	vtype = 0;
 
-	error = table_get_info(oh, xi);
+	if (xi->tablename[0] == '\0')
+		error = table_get_info(oh, xi);
+	else
+		error = 0;
 
 	if (error == 0) {
 		/* Table found. */

Modified: projects/ipfw/sys/netinet/ip_fw.h
==============================================================================
--- projects/ipfw/sys/netinet/ip_fw.h	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sys/netinet/ip_fw.h	Mon Aug 11 17:34:25 2014	(r269821)
@@ -587,7 +587,7 @@ struct ip_fw {
 	uint16_t	act_ofs;	/* offset of action in 32-bit units */
 	uint16_t	cmd_len;	/* # of 32-bit words in cmd	*/
 	uint16_t	rulenum;	/* rule number			*/
-	uint8_t	set;		/* rule set (0..31)		*/
+	uint8_t		set;		/* rule set (0..31)		*/
 	uint8_t		_pad;		/* padding			*/
 	uint32_t	id;		/* rule id */
 
@@ -784,7 +784,10 @@ typedef struct	_ipfw_obj_tentry {
 	uint8_t		masklen;	/* mask length			*/
 	uint16_t	idx;		/* Table name index		*/
 	uint32_t	value;		/* value			*/
-	uint64_t	spare;
+	uint8_t		result;		/* request result		*/
+	uint8_t		spare0;
+	uint16_t	spare1;
+	uint32_t	spare2;
 	union {
 		/* Longest field needs to be aligned by 8-byte boundary	*/
 		struct in_addr		addr;	/* IPv4 address		*/
@@ -795,6 +798,17 @@ typedef struct	_ipfw_obj_tentry {
 	} k;
 } ipfw_obj_tentry;
 #define	IPFW_TF_UPDATE	0x01		/* Update record if exists	*/
+/* Container TLV */
+#define	IPFW_CTF_ATOMIC	0x01		/* Perform atomic operation	*/
+/* Operation results */
+#define	IPFW_TR_IGNORED		0	/* Entry was ignored (rollback)	*/
+#define	IPFW_TR_ADDED		1	/* Entry was succesfully added	*/
+#define	IPFW_TR_UPDATED		2	/* Entry was succesfully updated*/
+#define	IPFW_TR_DELETED		3	/* Entry was succesfully deleted*/
+#define	IPFW_TR_LIMIT		4	/* Entry was ignored (limit)	*/
+#define	IPFW_TR_NOTFOUND	5	/* Entry was not found		*/
+#define	IPFW_TR_EXISTS		6	/* Entry already exists		*/
+#define	IPFW_TR_ERROR		7	/* Request has failed (unknown)	*/
 
 typedef struct _ipfw_obj_dyntlv {
 	ipfw_obj_tlv	head;
@@ -808,7 +822,7 @@ typedef struct _ipfw_obj_ctlv {
 	uint32_t	count;		/* Number of sub-TLVs		*/
 	uint16_t	objsize;	/* Single object size		*/
 	uint8_t		version;	/* TLV version			*/
-	uint8_t		spare;
+	uint8_t		flags;		/* TLV-specific flags		*/
 } ipfw_obj_ctlv;
 
 /* Range TLV */

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h	Mon Aug 11 17:34:25 2014	(r269821)
@@ -427,15 +427,18 @@ struct obj_idx {
 };
 
 struct rule_check_info {
+	uint16_t	flags;		/* rule-specific check flags */
 	uint16_t	table_opcodes;	/* count of opcodes referencing table */
-	uint16_t	new_tables;	/* count of opcodes referencing table */
 	uint16_t	urule_numoff;	/* offset of rulenum in bytes */
 	uint8_t		version;	/* rule version */
+	uint8_t		spare;
 	ipfw_obj_ctlv	*ctlv;		/* name TLV containter */
 	struct ip_fw	*krule;		/* resulting rule pointer */
 	caddr_t		urule;		/* original rule pointer */
 	struct obj_idx	obuf[8];	/* table references storage */
 };
+#define	IPFW_RCF_TABLES		0x01	/* Has table-referencing opcode */
+
 
 /* Legacy interface support */
 /*

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c	Mon Aug 11 17:34:25 2014	(r269821)
@@ -2267,7 +2267,7 @@ ipfw_get_sopt_header(struct sockopt_data
 int
 ipfw_ctl3(struct sockopt *sopt)
 {
-	int error;
+	int error, ctype;
 	size_t bsize_max, size, valsize;
 	struct ip_fw_chain *chain;
 	uint32_t opt;
@@ -2297,27 +2297,35 @@ ipfw_ctl3(struct sockopt *sopt)
 	sopt->sopt_valsize = valsize;
 
 	/*
+	 * Determine opcode type/buffer size:
+	 * use on-stack xbuf for short request,
+	 * allocate sliding-window buf for data export or
+	 * contigious buffer for special ops.
+	 */
+	ctype = (sopt->sopt_dir == SOPT_GET) ? SOPT_GET : SOPT_SET;
+	switch (opt) {
+	case IP_FW_XADD:
+	case IP_FW_XDEL:
+	case IP_FW_TABLE_XADD:
+	case IP_FW_TABLE_XDEL:
+		ctype = SOPT_SET;
+		bsize_max = IP_FW3_READBUF;
+		break;
+	default:
+		bsize_max = IP_FW3_WRITEBUF;
+	}
+
+	/*
 	 * Disallow modifications in really-really secure mode, but still allow
 	 * the logging counters to be reset.
 	 */
-	if (opt == IP_FW_XADD || opt == IP_FW_XDEL ||
-	    (sopt->sopt_dir == SOPT_SET && opt != IP_FW_XRESETLOG)) {
+	if (ctype == SOPT_SET && opt != IP_FW_XRESETLOG) {
 		error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
 		if (error != 0)
 			return (error);
 	}
 
 	/*
-	 * Determine buffer size:
-	 * use on-stack xbuf for short request,
-	 * allocate sliding-window buf for data export or
-	 * contigious buffer for special ops.
-	 */
-	bsize_max = IP_FW3_WRITEBUF;
-	if (opt == IP_FW_ADD)
-		bsize_max = IP_FW3_READBUF;
-
-	/*
 	 * Fill in sockopt_data structure that may be useful for
 	 * IP_FW3 get requests.
 	 */
@@ -2664,8 +2672,8 @@ ipfw_ctl(struct sockopt *sopt)
 			ti.type = IPFW_TABLE_CIDR;
 
 			error = (opt == IP_FW_TABLE_ADD) ?
-			    add_table_entry(chain, &ti, &tei, 1) :
-			    del_table_entry(chain, &ti, &tei, 1);
+			    add_table_entry(chain, &ti, &tei, 0, 1) :
+			    del_table_entry(chain, &ti, &tei, 0, 1);
 		}
 		break;
 

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c	Mon Aug 11 17:04:04 2014	(r269820)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c	Mon Aug 11 17:34:25 2014	(r269821)
@@ -143,19 +143,58 @@ static int classify_table_opcode(ipfw_in
 
 #define	TA_BUF_SZ	128	/* On-stack buffer for add/delete state */
 
+/*
+ * Checks if we're able to insert/update entry @tei into table
+ * w.r.t @tc limits.
+ * May alter @tei to indicate insertion error / insert
+ * options.
+ *
+ * Returns 0 if operation can be performed/
+ */
+static int
+check_table_limit(struct table_config *tc, struct tentry_info *tei)
+{
 
+	if (tc->limit == 0 || tc->count < tc->limit)
+		return (0);
+
+	if ((tei->flags & TEI_FLAGS_UPDATE) == 0) {
+		/* Notify userland on error cause */
+		tei->flags |= TEI_FLAGS_LIMIT;
+		return (EFBIG);
+	}
+
+	/*
+	 * We have UPDATE flag set.
+	 * Permit updating record (if found),
+	 * but restrict adding new one since we've
+	 * already hit the limit.
+	 */
+	tei->flags |= TEI_FLAGS_DONTADD;
+
+	return (0);
+}
+
+/*
+ * Adds/updates one or more entries in table @ti.
+ *
+ * Returns 0 on success.
+ */
 int
 add_table_entry(struct ip_fw_chain *ch, struct tid_info *ti,
-    struct tentry_info *tei, uint32_t count)
+    struct tentry_info *tei, uint8_t flags, uint32_t count)
 {
 	struct table_config *tc;
 	struct table_algo *ta;
 	struct namedobj_instance *ni;
 	uint16_t kidx;
-	int error;
-	uint32_t num;
+	int error, first_error, i, j, rerror, rollback;
+	uint32_t num, numadd;
 	ipfw_xtable_info *xi;
+	struct tentry_info *ptei;
 	char ta_buf[TA_BUF_SZ];
+	size_t ta_buf_sz;
+	caddr_t ta_buf_m, v, vv;
 
 	IPFW_UH_WLOCK(ch);
 	ni = CHAIN_TO_NI(ch);
@@ -172,8 +211,7 @@ add_table_entry(struct ip_fw_chain *ch, 
 		}
 
 		/* Try to exit early on limit hit */
-		if (tc->limit != 0 && tc->count >= tc->limit &&
-		    (tei->flags & TEI_FLAGS_UPDATE) == 0) {
+		if ((error = check_table_limit(tc, tei)) != 0 && count == 1) {
 				IPFW_UH_WUNLOCK(ch);
 				return (EFBIG);
 		}
@@ -218,10 +256,34 @@ add_table_entry(struct ip_fw_chain *ch, 
 	}
 
 	/* Prepare record (allocate memory) */
-	memset(&ta_buf, 0, sizeof(ta_buf));
-	error = ta->prepare_add(ch, tei, &ta_buf);
-	if (error != 0)
-		return (error);
+	ta_buf_sz = ta->ta_buf_size;
+	rollback = 0;
+	if (count == 1) {
+		memset(&ta_buf, 0, sizeof(ta_buf));
+		ta_buf_m = ta_buf;
+	} else {
+
+		/*
+		 * Multiple adds, allocate larger buffer
+		 * sufficient to hold both ADD state
+		 * and DELETE state (this may be needed
+		 * if we need to rollback all changes)
+		 */
+		ta_buf_m = malloc(2 * count * ta_buf_sz, M_TEMP,
+		    M_WAITOK | M_ZERO);
+	}
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz) {
+		error = ta->prepare_add(ch, &tei[i], v);
+
+		/*
+		 * Some syntax error (incorrect mask, or address, or
+		 * anything). Return error regardless of atomicity
+		 * settings.
+		 */
+		if (error != 0)
+			goto cleanup;
+	}
 
 	IPFW_UH_WLOCK(ch);
 
@@ -233,67 +295,142 @@ add_table_entry(struct ip_fw_chain *ch, 
 	error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), count);
 	if (error != 0) {
 		IPFW_UH_WUNLOCK(ch);
-		ta->flush_entry(ch, tei, &ta_buf);
-		return (error);
+		goto cleanup;
 	}
 
 	ni = CHAIN_TO_NI(ch);
 
 	/* Drop reference we've used in first search */
 	tc->no.refcnt--;
-	
-	/* Check limit before adding */
-	if (tc->limit != 0 && tc->count >= tc->limit) {
-		if ((tei->flags & TEI_FLAGS_UPDATE) == 0) {
-			IPFW_UH_WUNLOCK(ch);
-			ta->flush_entry(ch, tei, &ta_buf);
-			return (EFBIG);
+	/* We've got valid table in @tc. Let's try to add data */
+	kidx = tc->no.kidx;
+	ta = tc->ta;
+	numadd = 0;
+	first_error = 0;
+
+	IPFW_WLOCK(ch);
+
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz) {
+		ptei = &tei[i];
+		num = 0;
+		/* check limit before adding */
+		if ((error = check_table_limit(tc, ptei)) == 0) {
+			error = ta->add(tc->astate, KIDX_TO_TI(ch, kidx),
+			    ptei, v, &num);
+			/* Set status flag to inform userland */
+			if (error == 0 && num != 0)
+				ptei->flags |= TEI_FLAGS_ADDED;
+			else if (error == ENOENT)
+				ptei->flags |= TEI_FLAGS_NOTFOUND;
+			else if (error == EEXIST)
+				ptei->flags |= TEI_FLAGS_EXISTS;
+			else
+				ptei->flags |= TEI_FLAGS_ERROR;
+		}
+		if (error == 0) {
+			/* Update number of records to ease limit checking */
+			tc->count += num;
+			numadd += num;
+			continue;
 		}
 
+		if (first_error == 0)
+			first_error = error;
+
 		/*
-		 * We have UPDATE flag set.
-		 * Permit updating record (if found),
-		 * but restrict adding new one since we've
-		 * already hit the limit.
+		 * Some error have happened. Check our atomicity
+		 * settings: continue if atomicity is not required,
+		 * rollback changes otherwise.
 		 */
-		tei->flags |= TEI_FLAGS_DONTADD;
-	}
+		if ((flags & IPFW_CTF_ATOMIC) == 0)
+			continue;
 
-	/* We've got valid table in @tc. Let's add data */
-	kidx = tc->no.kidx;
-	ta = tc->ta;
-	num = 0;
+		/*
+		 * We need to rollback changes.
+		 * This is tricky since some entries may have been
+		 * updated, so  we need to change their value back
+		 * instead of deletion.
+		 */
+		rollback = 1;
+		v = ta_buf_m;
+		vv = v + count * ta_buf_sz;
+		for (j = 0; j < i; j++, v += ta_buf_sz, vv += ta_buf_sz) {
+			ptei = &tei[j];
+			if ((ptei->flags & TEI_FLAGS_UPDATED) != 0) {
 
-	IPFW_WLOCK(ch);
-	error = ta->add(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num);
-	IPFW_WUNLOCK(ch);
+				/*
+				 * We have old value stored by previous
+				 * call in @ptei->value. Do add once again
+				 * to restore it.
+				 */
+				rerror = ta->add(tc->astate,
+				    KIDX_TO_TI(ch, kidx), ptei, v, &num);
+				KASSERT(rerror == 0, ("rollback UPDATE fail"));
+				KASSERT(num == 0, ("rollback UPDATE fail2"));
+				continue;
+			}
 
-	/* Update number of records. */
-	if (error == 0) {
-		tc->count += num;
-		/* Permit post-add algorithm grow/rehash. */
-		error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0);
+			rerror = ta->prepare_del(ch, ptei, vv);
+			KASSERT(rerror == 0, ("pre-rollback INSERT failed"));
+			rerror = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), ptei,
+			    vv, &num);
+			KASSERT(rerror == 0, ("rollback INSERT failed"));
+			tc->count -= num;
+		}
+
+		break;
 	}
 
+	IPFW_WUNLOCK(ch);
+
+	/* Permit post-add algorithm grow/rehash. */
+	if (numadd != 0)
+		check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0);
+
 	IPFW_UH_WUNLOCK(ch);
 
+	/* Return first error to user, if any */
+	error = first_error;
+
+cleanup:
 	/* Run cleaning callback anyway */
-	ta->flush_entry(ch, tei, &ta_buf);
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz)
+		ta->flush_entry(ch, &tei[i], v);
+
+	/* Clean up "deleted" state in case of rollback */
+	if (rollback != 0) {
+		vv = ta_buf_m + count * ta_buf_sz;
+		for (i = 0; i < count; i++, vv += ta_buf_sz)
+			ta->flush_entry(ch, &tei[i], vv);
+	}
+
+	if (ta_buf_m != ta_buf)
+		free(ta_buf_m, M_TEMP);
 
 	return (error);
 }
 
+/*
+ * Deletes one or more entries in table @ti.
+ *
+ * Returns 0 on success.
+ */
 int
 del_table_entry(struct ip_fw_chain *ch, struct tid_info *ti,
-    struct tentry_info *tei, uint32_t count)
+    struct tentry_info *tei, uint8_t flags, uint32_t count)
 {
 	struct table_config *tc;
 	struct table_algo *ta;
 	struct namedobj_instance *ni;
+	struct tentry_info *ptei;
 	uint16_t kidx;
-	int error;
-	uint32_t num;
+	int error, first_error, i;
+	uint32_t num, numdel;
 	char ta_buf[TA_BUF_SZ];
+	size_t ta_buf_sz;
+	caddr_t ta_buf_m, v;
 
 	IPFW_UH_WLOCK(ch);
 	ni = CHAIN_TO_NI(ch);
@@ -307,8 +444,6 @@ del_table_entry(struct ip_fw_chain *ch, 
 		return (EINVAL);
 	}
 
-	ta = tc->ta;
-
 	/*
 	 * Give a chance for algorithm to shrink.
 	 * May release/reacquire UH_WLOCK.
@@ -317,36 +452,89 @@ del_table_entry(struct ip_fw_chain *ch, 
 	error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0);
 	if (error != 0) {
 		IPFW_UH_WUNLOCK(ch);
-		ta->flush_entry(ch, tei, &ta_buf);
 		return (error);
 	}
 
-	/*
-	 * We assume ta_buf size is enough for storing
-	 * prepare_del() key, so we're running under UH_WLOCK here.
-	 */
-	memset(&ta_buf, 0, sizeof(ta_buf));
-	if ((error = ta->prepare_del(ch, tei, &ta_buf)) != 0) {
-		IPFW_UH_WUNLOCK(ch);
-		return (error);
+	/* Reference and unlock */
+	tc->no.refcnt++;
+	ta = tc->ta;
+
+	IPFW_UH_WUNLOCK(ch);
+
+	/* Prepare record (allocate memory) */
+	ta_buf_sz = ta->ta_buf_size;
+	if (count == 1) {
+		memset(&ta_buf, 0, sizeof(ta_buf));
+		ta_buf_m = ta_buf;
+	} else {
+
+		/*
+		 * Multiple deletes, allocate larger buffer
+		 * sufficient to hold delete state.
+		 */
+		ta_buf_m = malloc(count * ta_buf_sz, M_TEMP,
+		    M_WAITOK | M_ZERO);
+	}
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz) {
+		error = ta->prepare_del(ch, &tei[i], v);
+
+		/*
+		 * Some syntax error (incorrect mask, or address, or
+		 * anything). Return error immediately.
+		 */
+		if (error != 0)
+			goto cleanup;
 	}
 
+	IPFW_UH_WLOCK(ch);
+
+	/* Drop reference we've used in first search */
+	tc->no.refcnt--;
+
 	kidx = tc->no.kidx;
-	num = 0;
+	numdel = 0;
+	first_error = 0;
 
 	IPFW_WLOCK(ch);
-	error = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), tei, &ta_buf, &num);
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz) {
+		ptei = &tei[i];
+		num = 0;
+		error = ta->del(tc->astate, KIDX_TO_TI(ch, kidx), ptei, v,
+		    &num);
+		/* Save state for userland */
+		if (error == 0)
+			ptei->flags |= TEI_FLAGS_DELETED;
+		else if (error == ENOENT)
+			ptei->flags |= TEI_FLAGS_NOTFOUND;
+		else
+			ptei->flags |= TEI_FLAGS_ERROR;
+		if (error != 0 && first_error == 0)
+			first_error = error;
+		tc->count -= num;
+		numdel += num;
+	}
 	IPFW_WUNLOCK(ch);
 
-	if (error == 0) {
-		tc->count -= num;
+	if (numdel != 0) {
 		/* Run post-del hook to permit shrinking */
 		error = check_table_space(ch, tc, KIDX_TO_TI(ch, kidx), 0);
 	}
 
 	IPFW_UH_WUNLOCK(ch);
 
-	ta->flush_entry(ch, tei, &ta_buf);
+	/* Return first error to user, if any */
+	error = first_error;
+
+cleanup:
+	/* Run cleaning callback anyway */
+	v = ta_buf_m;
+	for (i = 0; i < count; i++, v += ta_buf_sz)
+		ta->flush_entry(ch, &tei[i], v);
+
+	if (ta_buf_m != ta_buf)
+		free(ta_buf_m, M_TEMP);
 
 	return (error);
 }
@@ -432,8 +620,10 @@ check_table_space(struct ip_fw_chain *ch
 	return (error);
 }
 
-
-
+/*
+ * Selects appropriate table operation handler
+ * depending on opcode version.
+ */
 int
 ipfw_manage_table_ent(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
     struct sockopt_data *sd)
@@ -501,8 +691,8 @@ ipfw_manage_table_ent_v0(struct ip_fw_ch
 	ti.type = xent->type;
 
 	error = (op3->opcode == IP_FW_TABLE_XADD) ?
-	    add_table_entry(ch, &ti, &tei, 1) :
-	    del_table_entry(ch, &ti, &tei, 1);
+	    add_table_entry(ch, &ti, &tei, 0, 1) :
+	    del_table_entry(ch, &ti, &tei, 0, 1);
 
 	return (error);
 }
@@ -520,12 +710,12 @@ static int
 ipfw_manage_table_ent_v1(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
     struct sockopt_data *sd)
 {
-	ipfw_obj_tentry *tent;
+	ipfw_obj_tentry *tent, *ptent;
 	ipfw_obj_ctlv *ctlv;
 	ipfw_obj_header *oh;
-	struct tentry_info tei;
+	struct tentry_info *ptei, tei, *tei_buf;
 	struct tid_info ti;
-	int error, read;
+	int error, i, kidx, read;
 
 	/* Check minimum header size */
 	if (sd->valsize < (sizeof(*oh) + sizeof(*ctlv)))
@@ -547,37 +737,81 @@ ipfw_manage_table_ent_v1(struct ip_fw_ch
 	if (ctlv->head.length + read != sd->valsize)
 		return (EINVAL);
 
-	/*
-	 * TODO: permit adding multiple entries for given table
-	 * at once
-	 */
-	if (ctlv->count != 1)
-		return (EOPNOTSUPP);
-
 	read += sizeof(*ctlv);
-
-	/* Assume tentry may grow to support larger keys */
 	tent = (ipfw_obj_tentry *)(ctlv + 1);
-	if (tent->head.length < sizeof(*tent) ||
-	    tent->head.length + read > sd->valsize)
+	if (ctlv->count * sizeof(*tent) + read != sd->valsize)
 		return (EINVAL);
 
-	/* Convert data into kernel request objects */
-	memset(&tei, 0, sizeof(tei));
-	tei.paddr = &tent->k;
-	tei.subtype = tent->subtype;
-	tei.masklen = tent->masklen;
-	if (tent->head.flags & IPFW_TF_UPDATE)
-		tei.flags |= TEI_FLAGS_UPDATE;
-	tei.value = tent->value;
+	if (ctlv->count == 0)
+		return (0);
+
+	/*
+	 * Mark entire buffer as "read".
+	 * This makes sopt api write it back
+	 * after function return.
+	 */
+	ipfw_get_sopt_header(sd, sd->valsize);
+
+	/* Perform basic checks for each entry */
+	ptent = tent;
+	kidx = tent->idx;
+	for (i = 0; i < ctlv->count; i++, ptent++) {
+		if (ptent->head.length != sizeof(*ptent))
+			return (EINVAL);
+		if (ptent->idx != kidx)
+			return (ENOTSUP);
+	}
 
+	/* Convert data into kernel request objects */
 	objheader_to_ti(oh, &ti);
 	ti.type = oh->ntlv.type;
-	ti.uidx = tent->idx;
+	ti.uidx = kidx;
+
+	/* Use on-stack buffer for single add/del */
+	if (ctlv->count == 1) {
+		memset(&tei, 0, sizeof(tei));
+		tei_buf = &tei;
+	} else
+		tei_buf = malloc(ctlv->count * sizeof(tei), M_TEMP,
+		    M_WAITOK | M_ZERO);
+
+	ptei = tei_buf;
+	ptent = tent;
+	for (i = 0; i < ctlv->count; i++, ptent++, ptei++) {
+		ptei->paddr = &ptent->k;
+		ptei->subtype = ptent->subtype;
+		ptei->masklen = ptent->masklen;
+		if (ptent->head.flags & IPFW_TF_UPDATE)
+			ptei->flags |= TEI_FLAGS_UPDATE;
+		ptei->value = ptent->value;
+	}
 
 	error = (oh->opheader.opcode == IP_FW_TABLE_XADD) ?
-	    add_table_entry(ch, &ti, &tei, 1) :
-	    del_table_entry(ch, &ti, &tei, 1);
+	    add_table_entry(ch, &ti, tei_buf, ctlv->flags, ctlv->count) :
+	    del_table_entry(ch, &ti, tei_buf, ctlv->flags, ctlv->count);
+
+	/* Translate result back to userland */
+	ptei = tei_buf;
+	ptent = tent;
+	for (i = 0; i < ctlv->count; i++, ptent++, ptei++) {
+		if (ptei->flags & TEI_FLAGS_ADDED)
+			ptent->result = IPFW_TR_ADDED;
+		else if (ptei->flags & TEI_FLAGS_DELETED)
+			ptent->result = IPFW_TR_DELETED;
+		else if (ptei->flags & TEI_FLAGS_UPDATED)
+			ptent->result = IPFW_TR_UPDATED;
+		else if (ptei->flags & TEI_FLAGS_LIMIT)
+			ptent->result = IPFW_TR_LIMIT;
+		else if (ptei->flags & TEI_FLAGS_ERROR)
+			ptent->result = IPFW_TR_ERROR;

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?53e8fea2.296d.53afb556>