* 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
This commit is contained in:
Alexander V. Chernikov 2014-08-11 17:34:25 +00:00
parent 030b184f10
commit 3a845e1076
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/ipfw/; revision=269821
8 changed files with 570 additions and 164 deletions

View File

@ -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

View File

@ -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,68 +802,68 @@ table_flush_one(ipfw_xtable_info *i, void *arg)
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;
memcpy(ctlv + 1, tent, sizeof(*tent));
tent_base = tent;
memcpy(ctlv + 1, tent, sizeof(*tent) * count);
tent = (ipfw_obj_tentry *)(ctlv + 1);
if (update != 0)
tent->head.flags |= IPFW_TF_UPDATE;
tent->head.length = sizeof(ipfw_obj_tentry);
for (i = 0; i < count; i++, tent++) {
tent->head.length = sizeof(ipfw_obj_tentry);
tent->idx = oh->idx;
}
error = do_set3(cmd, &oh->opheader, sizeof(xbuf));
sz += sizeof(*oh);
error = do_get3(cmd, &oh->opheader, &sz);
tent = (ipfw_obj_tentry *)(ctlv + 1);
/* Copy result back to provided buffer */
memcpy(tent_base, ctlv + 1, sizeof(*tent) * count);
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;
tentry_fill_key(oh, &tent, *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) {
if (ac > 0)
tentry_fill_value(oh, &tent, *av, type, vtype);
cmd = IP_FW_TABLE_XADD;
texterr = "Adding record failed";
} else {
@ -854,7 +871,126 @@ table_modify_record(ipfw_obj_header *oh, int ac, char *av[], int add, int update
texterr = "Deleting record failed";
}
if ((error = table_do_modify_record(cmd, oh, &tent, update)) == 0)
/*
* 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++;
}
if (update != 0)
ptent->head.flags |= IPFW_TF_UPDATE;
count++;
ptent++;
}
error = table_do_modify_record(cmd, oh, tent_buf, count, atomic);
/*
* Compatibility stuff: do not yell on duplicate keys or
* failed deletions.
*/
if (error == 0 || (error == EEXIST && add != 0) ||
(error == ENOENT && add == 0)) {
if (quiet != 0) {
if (tent_buf != &tent)
free(tent_buf);
return;
}
}
/* 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 (error != 0 && atomic != 0 && ignored == 0)
printf("%s(reverted): ", px);
else
printf("%s: ", px);
table_show_entry(&xi, ptent);
}
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, char *av[])
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, ipfw_obj_tentry *tent, char *key,
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. */

View File

@ -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 */

View File

@ -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 */
/*

View File

@ -2267,7 +2267,7 @@ ipfw_get_sopt_header(struct sockopt_data *sd, size_t needed)
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,25 +2297,33 @@ ipfw_ctl3(struct sockopt *sopt)
sopt->sopt_valsize = valsize;
/*
* 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)) {
error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
if (error != 0)
return (error);
}
/*
* Determine buffer size:
* 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.
*/
bsize_max = IP_FW3_WRITEBUF;
if (opt == IP_FW_ADD)
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 (ctype == SOPT_SET && opt != IP_FW_XRESETLOG) {
error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
if (error != 0)
return (error);
}
/*
* Fill in sockopt_data structure that may be useful for
@ -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;

View File

@ -143,19 +143,58 @@ static int classify_table_opcode(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype
#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, struct tid_info *ti,
}
/* 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, struct tid_info *ti,
}
/* 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, struct tid_info *ti,
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 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;
}
/* We've got valid table in @tc. Let's add data */
/* We've got valid table in @tc. Let's try to add data */
kidx = tc->no.kidx;
ta = tc->ta;
num = 0;
numadd = 0;
first_error = 0;
IPFW_WLOCK(ch);
error = ta->add(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;
/* 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;
/*
* Some error have happened. Check our atomicity
* settings: continue if atomicity is not required,
* rollback changes otherwise.
*/
if ((flags & IPFW_CTF_ATOMIC) == 0)
continue;
/*
* 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) {
/*
* 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;
}
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);
/* 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);
}
/* 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, struct tid_info *ti,
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, struct tid_info *ti,
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, struct table_config *tc,
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_chain *ch, ip_fw3_opheader *op3,
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_chain *ch, ip_fw3_opheader *op3,
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;
else if (ptei->flags & TEI_FLAGS_NOTFOUND)
ptent->result = IPFW_TR_NOTFOUND;
else if (ptei->flags & TEI_FLAGS_EXISTS)
ptent->result = IPFW_TR_EXISTS;
}
if (tei_buf != &tei)
free(tei_buf, M_TEMP);
return (error);
}
@ -2364,7 +2598,7 @@ bind_table_rule(struct ip_fw_chain *ch, struct ip_fw *rule,
break;
}
ci->new_tables++;
ci->flags |= IPFW_RCF_TABLES;
pidx->new = 1;
pidx++;
}
@ -2742,7 +2976,7 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,
* Stage 2: allocate table configs for every non-existent table
*/
if (ci->new_tables > 0) {
if ((ci->flags & IPFW_RCF_TABLES) != 0) {
for (p = pidx_first; p < pidx_last; p++) {
if (p->new == 0)
continue;
@ -2789,7 +3023,7 @@ ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,
IPFW_UH_WLOCK(chain);
if (ci->new_tables > 0) {
if ((ci->flags & IPFW_RCF_TABLES) != 0) {
/*
* Stage 3: link & reference new table configs
*/

View File

@ -58,10 +58,16 @@ struct tentry_info {
uint16_t flags; /* record flags */
uint32_t value; /* value */
};
#define TEI_FLAGS_UPDATE 0x01 /* Add or update rec if exists */
#define TEI_FLAGS_UPDATED 0x02 /* Entry has been updated */
#define TEI_FLAGS_COMPAT 0x04 /* Called from old ABI */
#define TEI_FLAGS_DONTADD 0x08 /* Do not create new rec */
#define TEI_FLAGS_UPDATE 0x0001 /* Add or update rec if exists */
#define TEI_FLAGS_UPDATED 0x0002 /* Entry has been updated */
#define TEI_FLAGS_COMPAT 0x0004 /* Called from old ABI */
#define TEI_FLAGS_DONTADD 0x0008 /* Do not create new rec */
#define TEI_FLAGS_ADDED 0x0010 /* Entry was added */
#define TEI_FLAGS_DELETED 0x0020 /* Entry was deleted */
#define TEI_FLAGS_LIMIT 0x0040 /* Limit was hit */
#define TEI_FLAGS_ERROR 0x0080 /* Unknown request error */
#define TEI_FLAGS_NOTFOUND 0x0100 /* Entry was not found */
#define TEI_FLAGS_EXISTS 0x0200 /* Entry already exists */
typedef int (ta_init)(struct ip_fw_chain *ch, void **ta_state,
struct table_info *ti, char *data, uint8_t tflags);
@ -157,9 +163,9 @@ int ipfw_swap_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd);
/* Exported to support legacy opcodes */
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);
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);
int flush_table(struct ip_fw_chain *ch, struct tid_info *ti);
int ipfw_rewrite_table_uidx(struct ip_fw_chain *chain,

View File

@ -554,6 +554,9 @@ ta_del_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei,
rn = rnh->rnh_deladdr(tb->addr_ptr, tb->mask_ptr, rnh);
if (rn == NULL)
return (ENOENT);
/* Save entry value to @tei */
if (tei->subtype == AF_INET)
tei->value = ((struct radix_cidr_entry *)rn)->value;
@ -562,9 +565,6 @@ ta_del_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei,
tb->ent_ptr = rn;
if (rn == NULL)
return (ENOENT);
if (tei->subtype == AF_INET)
cfg->count4--;
else