svn commit: r269821 - in projects/ipfw: sbin/ipfw sys/netinet sys/netpfil/ipfw
Alexander V. Chernikov
melifaro at FreeBSD.org
Mon Aug 11 17:34:27 UTC 2014
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 ***
More information about the svn-src-projects
mailing list