svn commit: r268021 - in projects/ipfw: sbin/ipfw sys/netinet sys/netpfil/ipfw

Alexander V. Chernikov melifaro at FreeBSD.org
Sun Jun 29 22:35:49 UTC 2014


Author: melifaro
Date: Sun Jun 29 22:35:47 2014
New Revision: 268021
URL: http://svnweb.freebsd.org/changeset/base/268021

Log:
  * Add new IP_FW_XADD opcode which permits to
    a) specify table ids as names
    b) add multiple rules at once.
  Partially convert current code for atomic addition of multiple rules.

Modified:
  projects/ipfw/sbin/ipfw/ipfw2.c
  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

Modified: projects/ipfw/sbin/ipfw/ipfw2.c
==============================================================================
--- projects/ipfw/sbin/ipfw/ipfw2.c	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sbin/ipfw/ipfw2.c	Sun Jun 29 22:35:47 2014	(r268021)
@@ -2493,6 +2493,56 @@ lookup_host (char *host, struct in_addr 
 	return(0);
 }
 
+struct tidx {
+	ipfw_obj_ntlv *idx;
+	uint32_t count;
+	uint32_t size;
+	uint16_t counter;
+};
+
+static uint16_t
+pack_table(struct tidx *tstate, char *name, uint32_t set)
+{
+	char *p;
+	int i;
+	ipfw_obj_ntlv *ntlv;
+
+	if ((p = strchr(name, ')')) == NULL)
+		return (0);
+	*p = '\0';
+
+	if (table_check_name(name) != 0)
+		return (0);
+
+	for (i = 0; i < tstate->count; i++) {
+		if (strcmp(tstate->idx[i].name, name) != 0)
+			continue;
+		if (tstate->idx[i].set != set)
+			continue;
+
+		return (tstate->idx[i].idx);
+	}
+
+	if (tstate->count + 1 > tstate->size) {
+		tstate->size += 4;
+		tstate->idx = realloc(tstate->idx, tstate->size *
+		    sizeof(ipfw_obj_ntlv));
+		if (tstate->idx == NULL)
+			return (0);
+	}
+
+	ntlv = &tstate->idx[i];
+	memset(ntlv, 0, sizeof(ipfw_obj_ntlv));
+	strlcpy(ntlv->name, name, sizeof(ntlv->name));
+	ntlv->head.type = IPFW_TLV_TBL_NAME;
+	ntlv->head.length = sizeof(ipfw_obj_ntlv);
+	ntlv->set = set;
+	ntlv->idx = ++tstate->counter;
+	tstate->count++;
+
+	return (ntlv->idx);
+}
+
 /*
  * fills the addr and mask fields in the instruction as appropriate from av.
  * Update length as appropriate.
@@ -2505,10 +2555,12 @@ lookup_host (char *host, struct in_addr 
  * We can have multiple comma-separated address/mask entries.
  */
 static void
-fill_ip(ipfw_insn_ip *cmd, char *av, int cblen)
+fill_ip(ipfw_insn_ip *cmd, char *av, int cblen, struct tidx *tstate)
 {
 	int len = 0;
 	uint32_t *d = ((ipfw_insn_u32 *)cmd)->d;
+	uint16_t uidx;
+	char *p;
 
 	cmd->o.len &= ~F_LEN_MASK;	/* zero len */
 
@@ -2521,12 +2573,15 @@ fill_ip(ipfw_insn_ip *cmd, char *av, int
 	}
 
 	if (strncmp(av, "table(", 6) == 0) {
-		char *p = strchr(av + 6, ',');
-
+		p = strchr(av + 6, ',');
 		if (p)
 			*p++ = '\0';
+
+		if ((uidx = pack_table(tstate, av + 6, 0)) == 0)
+			errx(EX_DATAERR, "Invalid table name: %s", av + 6);
+
 		cmd->o.opcode = O_IP_DST_LOOKUP;
-		cmd->o.arg1 = strtoul(av + 6, NULL, 0);
+		cmd->o.arg1 = uidx;
 		if (p) {
 			cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32);
 			d[0] = strtoul(p, NULL, 0);
@@ -2805,8 +2860,10 @@ ipfw_delete(char *av[])
  * patterns which match interfaces.
  */
 static void
-fill_iface(ipfw_insn_if *cmd, char *arg, int cblen)
+fill_iface(ipfw_insn_if *cmd, char *arg, int cblen, struct tidx *tstate)
 {
+	uint16_t uidx;
+
 	cmd->name[0] = '\0';
 	cmd->o.len |= F_INSN_SIZE(ipfw_insn_if);
 
@@ -2819,8 +2876,11 @@ fill_iface(ipfw_insn_if *cmd, char *arg,
 		char *p = strchr(arg + 6, ',');
 		if (p)
 			*p++ = '\0';
+		if ((uidx = pack_table(tstate, arg + 6, 0)) == 0)
+			errx(EX_DATAERR, "Invalid table name: %s", arg + 6);
+
 		cmd->name[0] = '\1'; /* Special value indicating table */
-		cmd->p.glob = strtoul(arg + 6, NULL, 0);
+		cmd->p.glob = uidx;
 	} else if (!isdigit(*arg)) {
 		strlcpy(cmd->name, arg, sizeof(cmd->name));
 		cmd->p.glob = strpbrk(arg, "*?[") != NULL ? 1 : 0;
@@ -3034,9 +3094,9 @@ add_proto_compat(ipfw_insn *cmd, char *a
 }
 
 static ipfw_insn *
-add_srcip(ipfw_insn *cmd, char *av, int cblen)
+add_srcip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
 {
-	fill_ip((ipfw_insn_ip *)cmd, av, cblen);
+	fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
 	if (cmd->opcode == O_IP_DST_SET)			/* set */
 		cmd->opcode = O_IP_SRC_SET;
 	else if (cmd->opcode == O_IP_DST_LOOKUP)		/* table */
@@ -3051,9 +3111,9 @@ add_srcip(ipfw_insn *cmd, char *av, int 
 }
 
 static ipfw_insn *
-add_dstip(ipfw_insn *cmd, char *av, int cblen)
+add_dstip(ipfw_insn *cmd, char *av, int cblen, struct tidx *tstate)
 {
-	fill_ip((ipfw_insn_ip *)cmd, av, cblen);
+	fill_ip((ipfw_insn_ip *)cmd, av, cblen, tstate);
 	if (cmd->opcode == O_IP_DST_SET)			/* set */
 		;
 	else if (cmd->opcode == O_IP_DST_LOOKUP)		/* table */
@@ -3082,7 +3142,7 @@ add_ports(ipfw_insn *cmd, char *av, u_ch
 }
 
 static ipfw_insn *
-add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen)
+add_src(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
 {
 	struct in6_addr a;
 	char *host, *ch, buf[INET6_ADDRSTRLEN];
@@ -3105,7 +3165,7 @@ add_src(ipfw_insn *cmd, char *av, u_char
 	/* XXX: should check for IPv4, not !IPv6 */
 	if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
 	    inet_pton(AF_INET6, host, &a) != 1))
-		ret = add_srcip(cmd, av, cblen);
+		ret = add_srcip(cmd, av, cblen, tstate);
 	if (ret == NULL && strcmp(av, "any") != 0)
 		ret = cmd;
 
@@ -3113,7 +3173,7 @@ add_src(ipfw_insn *cmd, char *av, u_char
 }
 
 static ipfw_insn *
-add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen)
+add_dst(ipfw_insn *cmd, char *av, u_char proto, int cblen, struct tidx *tstate)
 {
 	struct in6_addr a;
 	char *host, *ch, buf[INET6_ADDRSTRLEN];
@@ -3136,7 +3196,7 @@ add_dst(ipfw_insn *cmd, char *av, u_char
 	/* XXX: should check for IPv4, not !IPv6 */
 	if (ret == NULL && (proto == IPPROTO_IP || strcmp(av, "me") == 0 ||
 	    inet_pton(AF_INET6, host, &a) != 1))
-		ret = add_dstip(cmd, av, cblen);
+		ret = add_dstip(cmd, av, cblen, tstate);
 	if (ret == NULL && strcmp(av, "any") != 0)
 		ret = cmd;
 
@@ -3156,7 +3216,7 @@ add_dst(ipfw_insn *cmd, char *av, u_char
  *
  */
 void
-ipfw_add(char *av[])
+compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
 {
 	/*
 	 * rules are added into the 'rulebuf' and then copied in
@@ -3164,7 +3224,7 @@ ipfw_add(char *av[])
 	 * Some things that need to go out of order (prob, action etc.)
 	 * go into actbuf[].
 	 */
-	static uint32_t rulebuf[255], actbuf[255], cmdbuf[255];
+	static uint32_t actbuf[255], cmdbuf[255];
 	int rblen, ablen, cblen;
 
 	ipfw_insn *src, *dst, *cmd, *action, *prev=NULL;
@@ -3190,14 +3250,14 @@ ipfw_add(char *av[])
 
 	bzero(actbuf, sizeof(actbuf));		/* actions go here */
 	bzero(cmdbuf, sizeof(cmdbuf));
-	bzero(rulebuf, sizeof(rulebuf));
+	bzero(rbuf, *rbufsize);
 
-	rule = (struct ip_fw *)rulebuf;
+	rule = (struct ip_fw *)rbuf;
 	cmd = (ipfw_insn *)cmdbuf;
 	action = (ipfw_insn *)actbuf;
 
-	rblen = sizeof(rulebuf) / sizeof(rulebuf[0]);
-	rblen -= offsetof(struct ip_fw, cmd) / sizeof(rulebuf[0]);
+	rblen = *rbufsize / sizeof(uint32_t);
+	rblen -= offsetof(struct ip_fw, cmd) / sizeof(uint32_t);
 	ablen = sizeof(actbuf) / sizeof(actbuf[0]);
 	cblen = sizeof(cmdbuf) / sizeof(cmdbuf[0]);
 	cblen -= F_INSN_SIZE(ipfw_insn_u32) + 1;
@@ -3685,7 +3745,7 @@ chkarg:
     OR_START(source_ip);
 	NOT_BLOCK;	/* optional "not" */
 	NEED1("missing source address");
-	if (add_src(cmd, *av, proto, cblen)) {
+	if (add_src(cmd, *av, proto, cblen, tstate)) {
 		av++;
 		if (F_LEN(cmd) != 0) {	/* ! any */
 			prev = cmd;
@@ -3721,7 +3781,7 @@ chkarg:
     OR_START(dest_ip);
 	NOT_BLOCK;	/* optional "not" */
 	NEED1("missing dst address");
-	if (add_dst(cmd, *av, proto, cblen)) {
+	if (add_dst(cmd, *av, proto, cblen, tstate)) {
 		av++;
 		if (F_LEN(cmd) != 0) {	/* ! any */
 			prev = cmd;
@@ -3828,7 +3888,7 @@ read_options:
 		case TOK_VIA:
 			NEED1("recv, xmit, via require interface name"
 				" or address");
-			fill_iface((ipfw_insn_if *)cmd, av[0], cblen);
+			fill_iface((ipfw_insn_if *)cmd, av[0], cblen, tstate);
 			av++;
 			if (F_LEN(cmd) == 0)	/* not a valid address */
 				break;
@@ -4074,14 +4134,14 @@ read_options:
 
 		case TOK_SRCIP:
 			NEED1("missing source IP");
-			if (add_srcip(cmd, *av, cblen)) {
+			if (add_srcip(cmd, *av, cblen, tstate)) {
 				av++;
 			}
 			break;
 
 		case TOK_DSTIP:
 			NEED1("missing destination IP");
-			if (add_dstip(cmd, *av, cblen)) {
+			if (add_dstip(cmd, *av, cblen, tstate)) {
 				av++;
 			}
 			break;
@@ -4323,17 +4383,111 @@ done:
 	}
 
 	rule->cmd_len = (uint32_t *)dst - (uint32_t *)(rule->cmd);
-	i = (char *)dst - (char *)rule;
-	if (do_cmd(IP_FW_ADD, rule, (uintptr_t)&i) == -1)
-		err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD");
+	*rbufsize = (char *)dst - (char *)rule;
+}
+
+/*
+ * Adds one or more rules to ipfw chain.
+ * Data layout:
+ * Request:
+ * [
+ *   ip_fw3_opheader
+ *   [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1)
+ *   [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] (*2) (*3)
+ * ]
+ * Reply:
+ * [
+ *   ip_fw3_opheader
+ *   [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional)
+ *   [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ]
+ * ]
+ *
+ * Rules in reply are modified to store their actual ruleset number.
+ *
+ * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending
+ * accoring to their idx field and there has to be no duplicates.
+ * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending.
+ * (*3) Each ip_fw structure needs to be aligned to u64 boundary.
+ */
+void
+ipfw_add(char *av[])
+{
+	uint32_t rulebuf[1024];
+	int rbufsize, default_off, tlen, rlen;
+	size_t sz;
+	struct tidx ts;
+	struct ip_fw *rule;
+	caddr_t tbuf;
+	ip_fw3_opheader *op3;
+	ipfw_obj_ctlv *ctlv, *tstate;
+
+	rbufsize = sizeof(rulebuf);
+	memset(&ts, 0, sizeof(ts));
+
+	/* Optimize case with no tables */
+	default_off = sizeof(ipfw_obj_ctlv) + sizeof(ip_fw3_opheader);
+	op3 = (ip_fw3_opheader *)rulebuf;
+	ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+	rule = (struct ip_fw *)(ctlv + 1);
+	rbufsize -= default_off;
+
+	compile_rule(av, (uint32_t *)rule, &rbufsize, &ts);
+	/* Align rule size to u64 boundary */
+	rlen = roundup2(rbufsize, sizeof(uint64_t));
+
+	tbuf = NULL;
+	sz = 0;
+	tstate = NULL;
+	if (ts.count != 0) {
+		/* Some tables. We have to alloc more data */
+		tlen = ts.count * sizeof(ipfw_obj_ntlv);
+		sz = default_off + sizeof(ipfw_obj_ctlv) + tlen + rlen;
+
+		if ((tbuf = calloc(1, sz)) == NULL)
+			err(EX_UNAVAILABLE, "malloc() failed for IP_FW_ADD");
+		op3 = (ip_fw3_opheader *)tbuf;
+		/* Tables first */
+		ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+		ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
+		ctlv->head.length = sizeof(ipfw_obj_ctlv) + tlen;
+		ctlv->count = ts.count;
+		ctlv->objsize = sizeof(ipfw_obj_ntlv);
+		memcpy(ctlv + 1, ts.idx, tlen);
+		table_sort_ctlv(ctlv);
+		tstate = ctlv;
+		/* Rule next */
+		ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+		ctlv->head.type = IPFW_TLV_RULE_LIST;
+		ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+		ctlv->count = 1;
+		memcpy(ctlv + 1, rule, rbufsize);
+	} else {
+		/* Simply add header */
+		sz = rlen + default_off;
+		memset(ctlv, 0, sizeof(*ctlv));
+		ctlv->head.type = IPFW_TLV_RULE_LIST;
+		ctlv->head.length = sizeof(ipfw_obj_ctlv) + rlen;
+		ctlv->count = 1;
+	}
+
+	if (do_get3(IP_FW_XADD, op3, &sz) != 0)
+		err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_XADD");
+
 	if (!co.do_quiet) {
 		struct format_opts sfo;
 		struct buf_pr bp;
 		memset(&sfo, 0, sizeof(sfo));
+		sfo.tstate = tstate;
 		bp_alloc(&bp, 4096);
 		show_static_rule(&co, &sfo, &bp, rule);
 		bp_free(&bp);
 	}
+
+	if (tbuf != NULL)
+		free(tbuf);
+
+	if (ts.idx != NULL)
+		free(ts.idx);
 }
 
 /*

Modified: projects/ipfw/sbin/ipfw/ipfw2.h
==============================================================================
--- projects/ipfw/sbin/ipfw/ipfw2.h	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sbin/ipfw/ipfw2.h	Sun Jun 29 22:35:47 2014	(r268021)
@@ -314,4 +314,6 @@ int fill_ext6hdr(struct _ipfw_insn *cmd,
 /* tables.c */
 struct _ipfw_obj_ctlv;
 char *table_search_ctlv(struct _ipfw_obj_ctlv *ctlv, uint16_t idx);
+void table_sort_ctlv(struct _ipfw_obj_ctlv *ctlv);
+int table_check_name(char *tablename);
 

Modified: projects/ipfw/sbin/ipfw/tables.c
==============================================================================
--- projects/ipfw/sbin/ipfw/tables.c	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sbin/ipfw/tables.c	Sun Jun 29 22:35:47 2014	(r268021)
@@ -593,9 +593,29 @@ table_show_list(ipfw_obj_header *oh, int
 	}
 }
 
+int
+compare_ntlv(const void *_a, const void *_b)
+{
+	ipfw_obj_ntlv *a, *b;
+
+	a = (ipfw_obj_ntlv *)_a;
+	b = (ipfw_obj_ntlv *)_b;
+
+	if (a->set < b->set)
+		return (-1);
+	else if (a->set > b->set)
+		return (1);
+
+	if (a->idx < b->idx)
+		return (-1);
+	else if (a->idx > b->idx)
+		return (1);
+
+	return (0);
+}
 
 int
-compare_ntlv(const void *k, const void *v)
+compare_kntlv(const void *k, const void *v)
 {
 	ipfw_obj_ntlv *ntlv;
 	uint16_t key;
@@ -625,7 +645,7 @@ table_search_ctlv(ipfw_obj_ctlv *ctlv, u
 	ipfw_obj_ntlv *ntlv;
 
 	ntlv = bsearch(&idx, (ctlv + 1), ctlv->count, ctlv->objsize,
-	    compare_ntlv);
+	    compare_kntlv);
 
 	if (ntlv != 0)
 		return (ntlv->name);
@@ -633,3 +653,37 @@ table_search_ctlv(ipfw_obj_ctlv *ctlv, u
 	return (NULL);
 }
 
+void
+table_sort_ctlv(ipfw_obj_ctlv *ctlv)
+{
+
+	qsort(ctlv + 1, ctlv->count, ctlv->objsize, compare_ntlv);
+}
+
+int
+table_check_name(char *tablename)
+{
+	int c, i, l;
+
+	/*
+	 * Check if tablename is null-terminated and contains
+	 * valid symbols only. Valid mask is:
+	 * [a-zA-Z\-\.][a-zA-Z0-9\-_\.]{0,62}
+	 */
+	l = strlen(tablename);
+	if (l == 0 || l >= 64)
+		return (EINVAL);
+	/* Restrict first symbol to non-digit */
+	if (isdigit(tablename[0]))
+		return (EINVAL);
+	for (i = 0; i < l; i++) {
+		c = tablename[i];
+		if (isalpha(c) || isdigit(c) || c == '_' ||
+		    c == '-' || c == '.')
+			continue;
+		return (EINVAL);	
+	}
+
+	return (0);
+}
+

Modified: projects/ipfw/sys/netinet/ip_fw.h
==============================================================================
--- projects/ipfw/sys/netinet/ip_fw.h	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sys/netinet/ip_fw.h	Sun Jun 29 22:35:47 2014	(r268021)
@@ -88,6 +88,7 @@ typedef struct _ip_fw3_opheader {
 #define	IP_FW_TABLE_XCREATE	95	/* create new table  */
 #define	IP_FW_TABLE_XMODIFY	96	/* modify existing table */
 #define	IP_FW_XGET		97	/* Retrieve configuration */
+#define	IP_FW_XADD		98	/* add entry */
 
 /*
  * Usage guidelines:
@@ -695,7 +696,7 @@ typedef struct _ipfw_obj_ntlv {
 	ipfw_obj_tlv	head;		/* TLV header			*/
 	uint16_t	idx;		/* Name index			*/
 	uint16_t	spare0;		/* unused			*/
-	uint32_t	spare1;		/* unused			*/
+	uint32_t	set;		/* set, if applicable		*/
 	char		name[64];	/* Null-terminated name		*/
 } ipfw_obj_ntlv;
 

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_private.h	Sun Jun 29 22:35:47 2014	(r268021)
@@ -331,10 +331,9 @@ struct rule_check_info {
 	uint16_t	table_opcodes;	/* count of opcodes referencing table */
 	uint16_t	new_tables;	/* count of opcodes referencing table */
 	uint32_t	tableset;	/* ipfw set id for table */
-	void		*tlvs;		/* Pointer to first TLV if any */
-	int		tlen;		/* *Total TLV size block */
-	uint8_t		fw3;		/* opcode is new */
+	ipfw_obj_ctlv	*ctlv;		/* name TLV containter */
 	struct ip_fw	*krule;		/* resulting rule pointer */
+	struct ip_fw	*urule;		/* original rule pointer */
 	struct obj_idx	obuf[8];	/* table references storage */
 };
 

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_sockopt.c	Sun Jun 29 22:35:47 2014	(r268021)
@@ -169,53 +169,92 @@ swap_map(struct ip_fw_chain *chain, stru
 }
 
 /*
- * Add a new rule to the list. Copy the rule into a malloc'ed area, then
- * possibly create a rule number and add the rule to the list.
+ * Copies rule @urule from userland format to kernel @krule.
+ */
+static void
+copy_rule(struct ip_fw *urule, struct ip_fw *krule)
+{
+	int l;
+
+	l = RULESIZE(urule);
+	bcopy(urule, krule, l);
+	/* clear fields not settable from userland */
+	krule->x_next = NULL;
+	krule->next_rule = NULL;
+	IPFW_ZERO_RULE_COUNTER(krule);
+}
+
+/*
+ * Add new rule(s) to the list possibly creating rule number for each.
  * Update the rule_number in the input struct so the caller knows it as well.
- * XXX DO NOT USE FOR THE DEFAULT RULE.
  * Must be called without IPFW_UH held
  */
 static int
-add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule,
-    struct rule_check_info *ci)
+commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count)
 {
-	struct ip_fw *rule;
-	int i, l, insert_before;
+	int error, i, l, insert_before, tcount;
+	struct rule_check_info *ci;
+	struct ip_fw *rule, *urule;
 	struct ip_fw **map;	/* the new array of pointers */
 
-	if (chain->map == NULL || input_rule->rulenum > IPFW_DEFAULT_RULE - 1)
-		return (EINVAL);
+	/* Check if we need to do table remap */
+	tcount = 0;
+	for (ci = rci, i = 0; i < count; ci++, i++) {
+		if (ci->table_opcodes == 0)
+			continue;
 
-	l = RULESIZE(input_rule);
-	rule = malloc(l, M_IPFW, M_WAITOK | M_ZERO);
-	bcopy(input_rule, rule, l);
-	/* clear fields not settable from userland */
-	rule->x_next = NULL;
-	rule->next_rule = NULL;
-	IPFW_ZERO_RULE_COUNTER(rule);
+		/*
+		 * Rule has some table opcodes.
+		 * Reference & allocate needed tables/
+		 */
+		error = ipfw_rewrite_table_uidx(chain, ci);
+		if (error != 0) {
 
-	/* Check if we need to do table remap */
-	if (ci->table_opcodes > 0) {
-		ci->krule = rule;
-		i = ipfw_rewrite_table_uidx(chain, ci);
-		if (i != 0) {
-			/* rewrite failed, return error */
-			free(rule, M_IPFW);
-			return (i);
+			/*
+			 * rewrite failed, state for current rule
+			 * has been reverted. Check if we need to
+			 * revert more.
+			 */
+			if (tcount > 0) {
+
+				/*
+				 * We have some more table rules
+				 * we need to rollback.
+				 */
+
+				IPFW_UH_WLOCK(chain);
+				while (ci != rci) {
+					ci--;
+					if (ci->table_opcodes == 0)
+						continue;
+					ipfw_unbind_table_rule(chain,ci->krule);
+
+				}
+				IPFW_UH_WUNLOCK(chain);
+
+			}
+
+			return (error);
 		}
+
+		tcount++;
 	}
 
 	/* get_map returns with IPFW_UH_WLOCK if successful */
-	map = get_map(chain, 1, 0 /* not locked */);
+	map = get_map(chain, count, 0 /* not locked */);
 	if (map == NULL) {
-		if (ci->table_opcodes > 0) {
-			/* We need to unbind tables */
+		if (tcount > 0) {
+			/* Unbind tables */
 			IPFW_UH_WLOCK(chain);
-			ipfw_unbind_table_rule(chain, rule);
+			for (ci = rci, i = 0; i < count; ci++, i++) {
+				if (ci->table_opcodes == 0)
+					continue;
+
+				ipfw_unbind_table_rule(chain, ci->krule);
+			}
 			IPFW_UH_WUNLOCK(chain);
 		}
 
-		free(rule, M_IPFW);
 		return (ENOSPC);
 	}
 
@@ -223,6 +262,13 @@ add_rule(struct ip_fw_chain *chain, stru
 		V_autoinc_step = 1;
 	else if (V_autoinc_step > 1000)
 		V_autoinc_step = 1000;
+
+	/* FIXME: Handle count > 1 */
+	ci = rci;
+	rule = ci->krule;
+	urule = ci->urule;
+	l = RULESIZE(rule);
+
 	/* find the insertion point, we will insert before */
 	insert_before = rule->rulenum ? rule->rulenum + 1 : IPFW_DEFAULT_RULE;
 	i = ipfw_find_rule(chain, insert_before, 0);
@@ -238,7 +284,7 @@ add_rule(struct ip_fw_chain *chain, stru
 		rule->rulenum = i > 0 ? map[i-1]->rulenum : 0;
 		if (rule->rulenum < IPFW_DEFAULT_RULE - V_autoinc_step)
 			rule->rulenum += V_autoinc_step;
-		input_rule->rulenum = rule->rulenum;
+		urule->rulenum = rule->rulenum;
 	}
 
 	rule->id = chain->id + 1;
@@ -581,6 +627,10 @@ check_ipfw_struct(struct ip_fw *rule, in
 		    rule->act_ofs, rule->cmd_len - 1);
 		return (EINVAL);
 	}
+
+	if (rule->rulenum > IPFW_DEFAULT_RULE - 1)
+		return (EINVAL);
+
 	/*
 	 * Now go for the individual checks. Very simple ones, basically only
 	 * instruction sizes.
@@ -1172,7 +1222,219 @@ dump_config(struct ip_fw_chain *chain, s
 }
 
 #define IP_FW3_OPLENGTH(x)	((x)->sopt_valsize - sizeof(ip_fw3_opheader))
-#define	IP_FW3_OPTBUF	4096	/* page-size */
+#define	IP_FW3_WRITEBUF	4096			/* small page-size write buffer */
+#define	IP_FW3_READBUF	16 * 1024 * 1024	/* handle large rulesets */
+
+
+static int
+check_object_name(ipfw_obj_ntlv *ntlv)
+{
+
+	if (strnlen(ntlv->name, sizeof(ntlv->name)) == sizeof(ntlv->name))
+		return (EINVAL);
+
+	/*
+	 * TODO: do some more complicated checks
+	 */
+
+	return (0);
+}
+
+/*
+ * Adds one or more rules to ipfw @chain.
+ * Data layout (version 0)(current):
+ * Request:
+ * [
+ *   ip_fw3_opheader
+ *   [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1)
+ *   [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] (*2) (*3)
+ * ]
+ * Reply:
+ * [
+ *   ip_fw3_opheader
+ *   [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional)
+ *   [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ]
+ * ]
+ *
+ * Rules in reply are modified to store their actual ruleset number.
+ *
+ * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending
+ * accoring to their idx field and there has to be no duplicates.
+ * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending.
+ * (*3) Each ip_fw structure needs to be aligned to u64 boundary.
+ *
+ * Returns 0 on success.
+ */
+static int
+add_entry(struct ip_fw_chain *chain, struct sockopt_data *sd)
+{
+	ipfw_obj_ctlv *ctlv, *rtlv, *tstate;
+	ipfw_obj_ntlv *ntlv;
+	int clen, error, idx;
+	uint32_t count, read;
+	struct ip_fw *r;
+	struct rule_check_info rci, *ci, *cbuf;
+	ip_fw3_opheader *op3;
+	int i, rsize;
+
+	if (sd->valsize > IP_FW3_READBUF)
+		return (EINVAL);
+
+	op3 = (ip_fw3_opheader *)ipfw_get_sopt_space(sd, sd->valsize);
+	ctlv = (ipfw_obj_ctlv *)(op3 + 1);
+
+	read = sizeof(ip_fw3_opheader);
+	rtlv = NULL;
+	tstate = NULL;
+	cbuf = NULL;
+	memset(&rci, 0, sizeof(struct rule_check_info));
+
+	if (read + sizeof(*ctlv) > sd->valsize)
+		return (EINVAL);
+
+	if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) {
+		clen = ctlv->head.length;
+		if (clen > sd->valsize || clen < sizeof(*ctlv))
+			return (EINVAL);
+
+		/*
+		 * Some table names or other named objects.
+		 * Check for validness.
+		 */
+		count = (ctlv->head.length - sizeof(*ctlv)) / sizeof(*ntlv);
+		if (ctlv->count != count || ctlv->objsize != sizeof(*ntlv))
+			return (EINVAL);
+
+		/*
+		 * Check each TLV.
+		 * Ensure TLVs are sorted ascending and
+		 * there are no duplicates.
+		 */
+		idx = -1;
+		ntlv = (ipfw_obj_ntlv *)(ctlv + 1);
+		while (count > 0) {
+			if (ntlv->head.length != sizeof(ipfw_obj_ntlv))
+				return (EINVAL);
+
+			error = check_object_name(ntlv);
+			if (error != 0)
+				return (error);
+
+			if (ntlv->idx <= idx)
+				return (EINVAL);
+
+			idx = ntlv->idx;
+			count--;
+			ntlv++;
+		}
+
+		tstate = ctlv;
+		read += ctlv->head.length;
+		ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+	}
+
+	if (read + sizeof(*ctlv) > sd->valsize)
+		return (EINVAL);
+
+	if (ctlv->head.type == IPFW_TLV_RULE_LIST) {
+		clen = ctlv->head.length;
+		if (clen + read > sd->valsize || clen < sizeof(*ctlv))
+			return (EINVAL);
+
+		/*
+		 * TODO: Permit adding multiple rules at once
+		 */
+		if (ctlv->count != 1)
+			return (ENOTSUP);
+
+		clen -= sizeof(*ctlv);
+
+		if (ctlv->count > clen / sizeof(struct ip_fw))
+			return (EINVAL);
+
+		/* Allocate state for each rule or use stack */
+		if (ctlv->count == 1) {
+			memset(&rci, 0, sizeof(struct rule_check_info));
+			cbuf = &rci;
+		} else
+			cbuf = malloc(ctlv->count * sizeof(*ci), M_TEMP,
+			    M_WAITOK | M_ZERO);
+		ci = cbuf;
+
+		/*
+		 * Check each rule for validness.
+		 * Ensure numbered rules are sorted ascending.
+		 */
+		idx = -1;
+		r = (struct ip_fw *)(ctlv + 1);
+		count = 0;
+		error = 0;
+		while (clen > 0) {
+			rsize = RULESIZE(r);
+			if (rsize > clen || ctlv->count <= count) {
+				error = EINVAL;
+				break;
+			}
+
+			ci->ctlv = tstate;
+			error = check_ipfw_struct(r, rsize, ci);
+			if (error != 0)
+				break;
+
+			/* Check sorting */
+			if (r->rulenum != 0 && r->rulenum < idx) {
+				error = EINVAL;
+				break;
+			}
+			idx = r->rulenum;
+
+			ci->urule = r;
+
+			rsize = roundup2(rsize, sizeof(uint64_t));
+			clen -= rsize;
+			r = (struct ip_fw *)((caddr_t)r + rsize);
+			count++;
+			ci++;
+		}
+
+		if (ctlv->count != count || error != 0) {
+			if (cbuf != &rci)
+				free(cbuf, M_TEMP);
+			return (EINVAL);
+		}
+
+		rtlv = ctlv;
+		read += ctlv->head.length;
+		ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
+	}
+
+	if (read != sd->valsize || rtlv == NULL || rtlv->count == 0) {
+		if (cbuf != NULL && cbuf != &rci)
+			free(cbuf, M_TEMP);
+		return (EINVAL);
+	}
+
+	/*
+	 * Passed rules seems to be valid.
+	 * Allocate storage and try to add them to chain.
+	 */
+	for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) {
+		ci->krule = malloc(RULESIZE(ci->urule), M_IPFW, M_WAITOK);
+		copy_rule(ci->urule, ci->krule);
+	}
+
+	if ((error = commit_rules(chain, cbuf, rtlv->count)) != 0) {
+		/* Free allocate krules */
+		for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++)
+			free(ci->krule, M_IPFW);
+	}
+
+	if (cbuf != NULL && cbuf != &rci)
+		free(cbuf, M_TEMP);
+
+	return (error);
+}
+
 /**
  * {set|get}sockopt parser.
  */
@@ -1181,7 +1443,7 @@ ipfw_ctl(struct sockopt *sopt)
 {
 #define	RULE_MAXSIZE	(256*sizeof(u_int32_t))
 	int error;
-	size_t size, len, valsize;
+	size_t bsize_max, size, len, valsize;
 	struct ip_fw *buf, *rule;
 	struct ip_fw_chain *chain;
 	u_int32_t rulenum[2];
@@ -1195,37 +1457,62 @@ ipfw_ctl(struct sockopt *sopt)
 	if (error)
 		return (error);
 
+	chain = &V_layer3_chain;
+	error = 0;
+
+	/* Save original valsize before it is altered via sooptcopyin() */
+	valsize = sopt->sopt_valsize;
+	memset(&sdata, 0, sizeof(sdata));
+	/* Read op3 header first to determine actual operation */
+	if ((opt = sopt->sopt_name) == IP_FW3) {
+		op3 = (ip_fw3_opheader *)xbuf;
+		error = sooptcopyin(sopt, op3, sizeof(*op3), sizeof(*op3));
+		if (error != 0)
+			return (error);
+		opt = op3->opcode;
+		sopt->sopt_valsize = valsize;
+	}
+
 	/*
 	 * Disallow modifications in really-really secure mode, but still allow
 	 * the logging counters to be reset.
 	 */
-	if (sopt->sopt_name == IP_FW_ADD ||
-	    (sopt->sopt_dir == SOPT_SET && sopt->sopt_name != IP_FW_RESETLOG)) {
+	if (opt == IP_FW_ADD ||
+	    (sopt->sopt_dir == SOPT_SET && opt != IP_FW_RESETLOG)) {
 		error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
-		if (error)
+		if (error != 0) {
+			if (sdata.kbuf != xbuf)
+				free(sdata.kbuf, M_TEMP);
 			return (error);
+		}
 	}
 
-	chain = &V_layer3_chain;
-	error = 0;
+	if (op3 != NULL) {
+
+		/*
+		 * 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;
 
-	/* Save original valsize before it is altered via sooptcopyin() */
-	valsize = sopt->sopt_valsize;
-	memset(&sdata, 0, sizeof(sdata));
-	if ((opt = sopt->sopt_name) == IP_FW3) {
 		/*
 		 * Fill in sockopt_data structure that may be useful for
-		 * IP_FW3 get requests
+		 * IP_FW3 get requests.
 		 */
+
 		if (valsize <= sizeof(xbuf)) {
 			sdata.kbuf = xbuf;
 			sdata.ksize = sizeof(xbuf);
 			sdata.kavail = valsize;
 		} else {
-			if (valsize < IP_FW3_OPTBUF)
+			if (valsize < bsize_max)
 				size = valsize;
 			else
-				size = IP_FW3_OPTBUF;
+				size = bsize_max;
 
 			sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
 			sdata.ksize = size;
@@ -1236,8 +1523,8 @@ ipfw_ctl(struct sockopt *sopt)
 		sdata.valsize = valsize;
 
 		/*
-		 * Copy either all request (if valsize < IP_FW3_OPTBUF)
-		 * or first IP_FW3_OPTBUF bytes to guarantee most consumers
+		 * Copy either all request (if valsize < bsize_max)
+		 * or first bsize_max bytes to guarantee most consumers
 		 * that all necessary data has been copied).
 		 * Anyway, copy not less than sizeof(ip_fw3_opheader).
 		 */
@@ -1287,6 +1574,10 @@ ipfw_ctl(struct sockopt *sopt)
 		error = dump_config(chain, &sdata);
 		break;
 
+	case IP_FW_XADD: /* IP_FW3 */
+		error = add_entry(chain, &sdata);
+		break;
+
 	case IP_FW_FLUSH:
 		/* locking is done within del_entry() */
 		error = del_entry(chain, 0); /* special case, rule=0, cmd=0 means all */
@@ -1324,7 +1615,12 @@ ipfw_ctl(struct sockopt *sopt)
 		}
 		if (error == 0) {
 			/* locking is done within add_rule() */
-			error = add_rule(chain, rule, &ci);
+			struct ip_fw *krule;
+			krule = malloc(RULESIZE(rule), M_IPFW, M_WAITOK);
+			copy_rule(rule, krule);
+			ci.urule = rule;
+			ci.krule = krule;
+			error = commit_rules(chain, &ci, 1);
 			size = RULESIZE(rule);
 			if (!error && sopt->sopt_dir == SOPT_GET) {
 				if (is7) {

Modified: projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c
==============================================================================
--- projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c	Sun Jun 29 19:22:49 2014	(r268020)
+++ projects/ipfw/sys/netpfil/ipfw/ip_fw_table.c	Sun Jun 29 22:35:47 2014	(r268021)
@@ -1623,8 +1623,10 @@ ipfw_rewrite_table_uidx(struct ip_fw_cha
 
 	memset(&ti, 0, sizeof(ti));

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


More information about the svn-src-projects mailing list