Make named objects set-aware. Now it is possible to create named

objects with the same name in different sets.

Add optional manage_sets() callback to objects rewriting framework.
It is intended to implement handler for moving and swapping named
object's sets. Add ipfw_obj_manage_sets() function that implements
generic sets handler. Use new callback to implement sets support for
lookup tables.
External actions objects are global and they don't support sets.
Modify eaction_findbyname() to reflect this.
ipfw(8) now may fail to move rules or sets, because some named objects
in target set may have conflicting names.
Note that ipfw_obj_ntlv type was changed, but since lookup tables
actually didn't support sets, this change is harmless.

Obtained from:	Yandex LLC
Sponsored by:	Yandex LLC
This commit is contained in:
ae 2016-05-17 07:47:23 +00:00
parent eba4e79241
commit f79f8e9de8
6 changed files with 498 additions and 298 deletions

View File

@ -2280,6 +2280,9 @@ ipfw_sets_handler(char *av[])
if (!isdigit(*(av[2])) || rt.new_set > RESVD_SET)
errx(EX_DATAERR, "invalid dest. set %s\n", av[1]);
i = do_range_cmd(cmd, &rt);
if (i < 0)
err(EX_OSERR, "failed to move %s",
cmd == IP_FW_SET_MOVE ? "set": "rule");
} else if (_substrcmp(*av, "disable") == 0 ||
_substrcmp(*av, "enable") == 0 ) {
int which = _substrcmp(*av, "enable") == 0 ? 1 : 0;

View File

@ -791,9 +791,9 @@ typedef struct _ipfw_obj_tlv {
typedef struct _ipfw_obj_ntlv {
ipfw_obj_tlv head; /* TLV header */
uint16_t idx; /* Name index */
uint8_t spare; /* unused */
uint8_t set; /* set, if applicable */
uint8_t type; /* object type, if applicable */
uint32_t set; /* set, if applicable */
uint32_t spare; /* unused */
char name[64]; /* Null-terminated name */
} ipfw_obj_ntlv;

View File

@ -137,10 +137,28 @@ static int
eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti,
struct named_object **pno)
{
ipfw_obj_ntlv *ntlv;
EACTION_DEBUG("uidx %u, type %u", ti->uidx, ti->type);
return (ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti,
IPFW_TLV_EACTION, pno));
if (ti->tlvs == NULL)
return (EINVAL);
/* Search ntlv in the buffer provided by user */
ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx,
IPFW_TLV_EACTION);
if (ntlv == NULL)
return (EINVAL);
EACTION_DEBUG("name %s, uidx %u, type %u", ntlv->name,
ti->uidx, ti->type);
/*
* Search named object with corresponding name.
* Since eaction objects are global - ignore the set value
* and use zero instead.
*/
*pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch),
0, IPFW_TLV_EACTION, ntlv->name);
if (*pno == NULL)
return (ESRCH);
return (0);
}
static struct named_object *

View File

@ -315,9 +315,10 @@ struct named_object {
char *name; /* object name */
uint16_t etlv; /* Export TLV id */
uint8_t subtype;/* object subtype within class */
uint8_t spare[3];
uint8_t set; /* set object belongs to */
uint16_t kidx; /* object kernel index */
uint32_t set; /* set object belongs to */
uint16_t spare;
uint32_t ocnt; /* object counter for internal use */
uint32_t refcnt; /* number of references */
};
TAILQ_HEAD(namedobjects_head, named_object);
@ -571,6 +572,21 @@ typedef int (ipfw_obj_create_cb)(struct ip_fw_chain *ch, struct tid_info *ti,
*/
typedef void (ipfw_obj_destroy_cb)(struct ip_fw_chain *ch,
struct named_object *no);
/*
* Sets handler callback. Handles moving and swaping set of named object.
* SWAP_ALL moves all named objects from set `set' to `new_set' and vise versa;
* TEST_ALL checks that there aren't any named object with conflicting names;
* MOVE_ALL moves all named objects from set `set' to `new_set';
* COUNT_ONE used to count number of references used by object with kidx `set';
* TEST_ONE checks that named object with kidx `set' can be moved to `new_set`;
* MOVE_ONE moves named object with kidx `set' to set `new_set'.
*/
enum ipfw_sets_cmd {
SWAP_ALL = 0, TEST_ALL, MOVE_ALL, COUNT_ONE, TEST_ONE, MOVE_ONE
};
typedef int (ipfw_obj_sets_cb)(struct ip_fw_chain *ch,
uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd);
struct opcode_obj_rewrite {
uint32_t opcode; /* Opcode to act upon */
@ -581,6 +597,7 @@ struct opcode_obj_rewrite {
ipfw_obj_fidx_cb *find_bykidx; /* Find named object by kidx */
ipfw_obj_create_cb *create_object; /* Create named object */
ipfw_obj_destroy_cb *destroy_object;/* Destroy named object */
ipfw_obj_sets_cb *manage_sets; /* Swap or move sets */
};
#define IPFW_ADD_OBJ_REWRITER(f, c) do { \
@ -675,8 +692,11 @@ int ipfw_objhash_same_name(struct namedobj_instance *ni, struct named_object *a,
void ipfw_objhash_add(struct namedobj_instance *ni, struct named_object *no);
void ipfw_objhash_del(struct namedobj_instance *ni, struct named_object *no);
uint32_t ipfw_objhash_count(struct namedobj_instance *ni);
uint32_t ipfw_objhash_count_type(struct namedobj_instance *ni, uint16_t type);
int ipfw_objhash_foreach(struct namedobj_instance *ni, objhash_cb_t *f,
void *arg);
int ipfw_objhash_foreach_type(struct namedobj_instance *ni, objhash_cb_t *f,
void *arg, uint16_t type);
int ipfw_objhash_free_idx(struct namedobj_instance *ni, uint16_t idx);
int ipfw_objhash_alloc_idx(void *n, uint16_t *pidx);
void ipfw_objhash_set_funcs(struct namedobj_instance *ni,
@ -698,6 +718,8 @@ int classify_opcode_kidx(ipfw_insn *cmd, uint16_t *puidx);
void ipfw_init_srv(struct ip_fw_chain *ch);
void ipfw_destroy_srv(struct ip_fw_chain *ch);
int ipfw_check_object_name_generic(const char *name);
int ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type,
uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd);
/* In ip_fw_eaction.c */
typedef int (ipfw_eaction_t)(struct ip_fw_chain *ch, struct ip_fw_args *args,

View File

@ -851,6 +851,113 @@ ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt)
return (1);
}
struct manage_sets_args {
uint16_t set;
uint8_t new_set;
};
static int
swap_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set == (uint8_t)args->set)
no->set = args->new_set;
else if (no->set == args->new_set)
no->set = (uint8_t)args->set;
return (0);
}
static int
move_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set == (uint8_t)args->set)
no->set = args->new_set;
return (0);
}
static int
test_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set != (uint8_t)args->set)
return (0);
if (ipfw_objhash_lookup_name_type(ni, args->new_set,
no->etlv, no->name) != NULL)
return (EEXIST);
return (0);
}
/*
* Generic function to handler moving and swapping sets.
*/
int
ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type,
uint16_t set, uint8_t new_set, enum ipfw_sets_cmd cmd)
{
struct manage_sets_args args;
struct named_object *no;
args.set = set;
args.new_set = new_set;
switch (cmd) {
case SWAP_ALL:
return (ipfw_objhash_foreach_type(ni, swap_sets_cb,
&args, type));
case TEST_ALL:
return (ipfw_objhash_foreach_type(ni, test_sets_cb,
&args, type));
case MOVE_ALL:
return (ipfw_objhash_foreach_type(ni, move_sets_cb,
&args, type));
case COUNT_ONE:
/*
* @set used to pass kidx.
* When @new_set is zero - reset object counter,
* otherwise increment it.
*/
no = ipfw_objhash_lookup_kidx(ni, set);
if (new_set != 0)
no->ocnt++;
else
no->ocnt = 0;
return (0);
case TEST_ONE:
/* @set used to pass kidx */
no = ipfw_objhash_lookup_kidx(ni, set);
/*
* First check number of references:
* when it differs, this mean other rules are holding
* reference to given object, so it is not possible to
* change its set. Note that refcnt may account references
* to some going-to-be-added rules. Since we don't know
* their numbers (and even if they will be added) it is
* perfectly OK to return error here.
*/
if (no->ocnt != no->refcnt)
return (EBUSY);
if (ipfw_objhash_lookup_name_type(ni, new_set, type,
no->name) != NULL)
return (EEXIST);
return (0);
case MOVE_ONE:
/* @set used to pass kidx */
no = ipfw_objhash_lookup_kidx(ni, set);
no->set = new_set;
return (0);
}
return (EINVAL);
}
/*
* Delete rules matching range @rt.
* Saves number of deleted rules in @ndel.
@ -935,7 +1042,89 @@ delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel)
return (0);
}
/*
static int
move_objects(struct ip_fw_chain *ch, ipfw_range_tlv *rt)
{
struct opcode_obj_rewrite *rw;
struct ip_fw *rule;
ipfw_insn *cmd;
int cmdlen, i, l, c;
uint16_t kidx;
IPFW_UH_WLOCK_ASSERT(ch);
/* Stage 1: count number of references by given rules */
for (c = 0, i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set) /* nothing to do */
continue;
/* Search opcodes with named objects */
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
/*
* When manage_sets() returns non-zero value to
* COUNT_ONE command, consider this as an object
* doesn't support sets (e.g. disabled with sysctl).
* So, skip checks for this object.
*/
if (rw->manage_sets(ch, kidx, 1, COUNT_ONE) != 0)
continue;
c++;
}
}
if (c == 0) /* No objects found */
return (0);
/* Stage 2: verify "ownership" */
for (c = 0, i = 0; (i < ch->n_rules - 1) && c == 0; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set) /* nothing to do */
continue;
/* Search opcodes with named objects */
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0 && c == 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
/* Test for ownership and conflicting names */
c = rw->manage_sets(ch, kidx,
(uint8_t)rt->new_set, TEST_ONE);
}
}
/* Stage 3: change set and cleanup */
for (i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set) /* nothing to do */
continue;
/* Search opcodes with named objects */
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
/* cleanup object counter */
rw->manage_sets(ch, kidx,
0 /* reset counter */, COUNT_ONE);
if (c != 0)
continue;
/* change set */
rw->manage_sets(ch, kidx,
(uint8_t)rt->new_set, MOVE_ONE);
}
}
return (c);
}/*
* Changes set of given rule rannge @rt
* with each other.
*
@ -956,11 +1145,9 @@ move_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
* by given rule subset only. Otherwise, we can't move
* them to new set and have to return error.
*/
if (V_fw_tables_sets != 0) {
if (ipfw_move_tables_sets(chain, rt, rt->new_set) != 0) {
IPFW_UH_WUNLOCK(chain);
return (EBUSY);
}
if ((i = move_objects(chain, rt)) != 0) {
IPFW_UH_WUNLOCK(chain);
return (i);
}
/* XXX: We have to do swap holding WLOCK */
@ -1156,24 +1343,48 @@ enable_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
IPFW_WUNLOCK(chain);
}
static void
static int
swap_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int mv)
{
struct opcode_obj_rewrite *rw;
struct ip_fw *rule;
int i;
IPFW_UH_WLOCK_ASSERT(chain);
if (rt->set == rt->new_set) /* nothing to do */
return (0);
if (mv != 0) {
/*
* Berfore moving the rules we need to check that
* there aren't any conflicting named objects.
*/
for (rw = ctl3_rewriters;
rw < ctl3_rewriters + ctl3_rsize; rw++) {
if (rw->manage_sets == NULL)
continue;
i = rw->manage_sets(chain, (uint8_t)rt->set,
(uint8_t)rt->new_set, TEST_ALL);
if (i != 0)
return (EEXIST);
}
}
/* Swap or move two sets */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (rule->set == rt->set)
rule->set = rt->new_set;
else if (rule->set == rt->new_set && mv == 0)
rule->set = rt->set;
if (rule->set == (uint8_t)rt->set)
rule->set = (uint8_t)rt->new_set;
else if (rule->set == (uint8_t)rt->new_set && mv == 0)
rule->set = (uint8_t)rt->set;
}
if (V_fw_tables_sets != 0)
ipfw_swap_tables_sets(chain, rt->set, rt->new_set, mv);
for (rw = ctl3_rewriters; rw < ctl3_rewriters + ctl3_rsize; rw++) {
if (rw->manage_sets == NULL)
continue;
rw->manage_sets(chain, (uint8_t)rt->set,
(uint8_t)rt->new_set, mv != 0 ? MOVE_ALL: SWAP_ALL);
}
return (0);
}
/*
@ -1188,6 +1399,7 @@ manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int ret;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
@ -1196,12 +1408,17 @@ manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
if (rh->range.head.length != sizeof(ipfw_range_tlv))
return (1);
if (rh->range.set >= IPFW_MAX_SETS ||
rh->range.new_set >= IPFW_MAX_SETS)
return (EINVAL);
ret = 0;
IPFW_UH_WLOCK(chain);
switch (op3->opcode) {
case IP_FW_SET_SWAP:
case IP_FW_SET_MOVE:
swap_sets(chain, &rh->range, op3->opcode == IP_FW_SET_MOVE);
ret = swap_sets(chain, &rh->range,
op3->opcode == IP_FW_SET_MOVE);
break;
case IP_FW_SET_ENABLE:
enable_sets(chain, &rh->range);
@ -1209,7 +1426,7 @@ manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
}
IPFW_UH_WUNLOCK(chain);
return (0);
return (ret);
}
/**
@ -1280,14 +1497,14 @@ del_entry(struct ip_fw_chain *chain, uint32_t arg)
break;
case 3: /* move rules from set "rulenum" to set "new_set" */
IPFW_UH_WLOCK(chain);
swap_sets(chain, &rt, 1);
error = swap_sets(chain, &rt, 1);
IPFW_UH_WUNLOCK(chain);
return (0);
return (error);
case 4: /* swap sets "rulenum" and "new_set" */
IPFW_UH_WLOCK(chain);
swap_sets(chain, &rt, 0);
error = swap_sets(chain, &rt, 0);
IPFW_UH_WUNLOCK(chain);
return (0);
return (error);
default:
return (ENOTSUP);
}
@ -2526,11 +2743,8 @@ rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci)
type = 0;
memset(&ti, 0, sizeof(ti));
/*
* Use default set for looking up tables (old way) or
* use set rule is assigned to (new way).
*/
ti.set = (V_fw_tables_sets != 0) ? ci->krule->set : 0;
/* Use set rule is assigned to. */
ti.set = ci->krule->set;
if (ci->ctlv != NULL) {
ti.tlvs = (void *)(ci->ctlv + 1);
ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv);
@ -4248,6 +4462,23 @@ ipfw_objhash_count(struct namedobj_instance *ni)
return (ni->count);
}
uint32_t
ipfw_objhash_count_type(struct namedobj_instance *ni, uint16_t type)
{
struct named_object *no;
uint32_t count;
int i;
count = 0;
for (i = 0; i < ni->nn_size; i++) {
TAILQ_FOREACH(no, &ni->names[i], nn_next) {
if (no->etlv == type)
count++;
}
}
return (count);
}
/*
* Runs @func for each found named object.
* It is safe to delete objects from callback
@ -4268,6 +4499,29 @@ ipfw_objhash_foreach(struct namedobj_instance *ni, objhash_cb_t *f, void *arg)
return (0);
}
/*
* Runs @f for each found named object with type @type.
* It is safe to delete objects from callback
*/
int
ipfw_objhash_foreach_type(struct namedobj_instance *ni, objhash_cb_t *f,
void *arg, uint16_t type)
{
struct named_object *no, *no_tmp;
int i, ret;
for (i = 0; i < ni->nn_size; i++) {
TAILQ_FOREACH_SAFE(no, &ni->names[i], nn_next, no_tmp) {
if (no->etlv != type)
continue;
ret = f(ni, no, arg);
if (ret != 0)
return (ret);
}
}
return (0);
}
/*
* Removes index from given set.
* Returns 0 on success.

View File

@ -1601,64 +1601,6 @@ ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables)
return (0);
}
/*
* Switch between "set 0" and "rule's set" table binding,
* Check all ruleset bindings and permits changing
* IFF each binding has both rule AND table in default set (set 0).
*
* Returns 0 on success.
*/
int
ipfw_switch_tables_namespace(struct ip_fw_chain *ch, unsigned int sets)
{
struct namedobj_instance *ni;
struct named_object *no;
struct ip_fw *rule;
ipfw_insn *cmd;
int cmdlen, i, l;
uint16_t kidx;
IPFW_UH_WLOCK(ch);
if (V_fw_tables_sets == sets) {
IPFW_UH_WUNLOCK(ch);
return (0);
}
ni = CHAIN_TO_NI(ch);
/*
* Scan all rules and examine tables opcodes.
*/
for (i = 0; i < ch->n_rules; i++) {
rule = ch->map[i];
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (classify_opcode_kidx(cmd, &kidx) != 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
/* Check if both table object and rule has the set 0 */
if (no->set != 0 || rule->set != 0) {
IPFW_UH_WUNLOCK(ch);
return (EBUSY);
}
}
}
V_fw_tables_sets = sets;
IPFW_UH_WUNLOCK(ch);
return (0);
}
/*
* Lookup an IP @addr in table @tbl.
* Stores found value in @val.
@ -2875,39 +2817,190 @@ table_findbykidx(struct ip_fw_chain *ch, uint16_t idx)
return (&tc->no);
}
static int
table_manage_sets(struct ip_fw_chain *ch, uint16_t set, uint8_t new_set,
enum ipfw_sets_cmd cmd)
{
switch (cmd) {
case SWAP_ALL:
case TEST_ALL:
/*
* Return success for TEST_ALL, since nothing prevents
* move rules from one set to another. All tables are
* accessible from all sets when per-set tables sysctl
* is disabled.
*/
case MOVE_ALL:
case TEST_ONE:
case MOVE_ONE:
/*
* NOTE: we need to use ipfw_objhash_del/ipfw_objhash_add
* if set number will be used in hash function. Currently
* we can just use generic handler that replaces set value.
*/
if (V_fw_tables_sets == 0)
return (0);
break;
case COUNT_ONE:
/*
* Return EOPNOTSUPP for COUNT_ONE when per-set sysctl is
* disabled. This allow skip table's opcodes from additional
* checks when specific rules moved to another set.
*/
if (V_fw_tables_sets == 0)
return (EOPNOTSUPP);
}
/* Use generic sets handler when per-set sysctl is enabled. */
return (ipfw_obj_manage_sets(CHAIN_TO_NI(ch), IPFW_TLV_TBL_NAME,
set, new_set, cmd));
}
static struct opcode_obj_rewrite opcodes[] = {
{
O_IP_SRC_LOOKUP, IPFW_TLV_TBL_NAME,
classify_srcdst, update_arg1,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_IP_SRC_LOOKUP,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_srcdst,
.update = update_arg1,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
{
O_IP_DST_LOOKUP, IPFW_TLV_TBL_NAME,
classify_srcdst, update_arg1,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_IP_DST_LOOKUP,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_srcdst,
.update = update_arg1,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
{
O_IP_FLOW_LOOKUP, IPFW_TLV_TBL_NAME,
classify_flow, update_arg1,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_IP_FLOW_LOOKUP,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_flow,
.update = update_arg1,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
{
O_XMIT, IPFW_TLV_TBL_NAME,
classify_via, update_via,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_XMIT,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_via,
.update = update_via,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
{
O_RECV, IPFW_TLV_TBL_NAME,
classify_via, update_via,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_RECV,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_via,
.update = update_via,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
{
O_VIA, IPFW_TLV_TBL_NAME,
classify_via, update_via,
table_findbyname, table_findbykidx, create_table_compat
.opcode = O_VIA,
.etlv = IPFW_TLV_TBL_NAME,
.classifier = classify_via,
.update = update_via,
.find_byname = table_findbyname,
.find_bykidx = table_findbykidx,
.create_object = create_table_compat,
.manage_sets = table_manage_sets,
},
};
static int
test_sets_cb(struct namedobj_instance *ni __unused, struct named_object *no,
void *arg __unused)
{
/* Check that there aren't any tables in not default set */
if (no->set != 0)
return (EBUSY);
return (0);
}
/*
* Switch between "set 0" and "rule's set" table binding,
* Check all ruleset bindings and permits changing
* IFF each binding has both rule AND table in default set (set 0).
*
* Returns 0 on success.
*/
int
ipfw_switch_tables_namespace(struct ip_fw_chain *ch, unsigned int sets)
{
struct opcode_obj_rewrite *rw;
struct namedobj_instance *ni;
struct named_object *no;
struct ip_fw *rule;
ipfw_insn *cmd;
int cmdlen, i, l;
uint16_t kidx;
uint8_t subtype;
IPFW_UH_WLOCK(ch);
if (V_fw_tables_sets == sets) {
IPFW_UH_WUNLOCK(ch);
return (0);
}
ni = CHAIN_TO_NI(ch);
if (sets == 0) {
/*
* Prevent disabling sets support if we have some tables
* in not default sets.
*/
if (ipfw_objhash_foreach_type(ni, test_sets_cb,
NULL, IPFW_TLV_TBL_NAME) != 0) {
IPFW_UH_WUNLOCK(ch);
return (EBUSY);
}
}
/*
* Scan all rules and examine tables opcodes.
*/
for (i = 0; i < ch->n_rules; i++) {
rule = ch->map[i];
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
/* Check only tables opcodes */
for (kidx = 0, rw = opcodes;
rw < opcodes + nitems(opcodes); rw++) {
if (rw->opcode != cmd->opcode)
continue;
if (rw->classifier(cmd, &kidx, &subtype) == 0)
break;
}
if (kidx == 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
/* Check if both table object and rule has the set 0 */
if (no->set != 0 || rule->set != 0) {
IPFW_UH_WUNLOCK(ch);
return (EBUSY);
}
}
}
V_fw_tables_sets = sets;
IPFW_UH_WUNLOCK(ch);
return (0);
}
/*
* Checks table name for validity.
@ -2954,7 +3047,7 @@ find_table_err(struct namedobj_instance *ni, struct tid_info *ti,
* This is needed due to different sets behavior
* controlled by V_fw_tables_sets.
*/
set = ti->set;
set = (V_fw_tables_sets != 0) ? ti->set : 0;
} else {
snprintf(bname, sizeof(bname), "%d", ti->uidx);
name = bname;
@ -3112,196 +3205,6 @@ unlink_table(struct ip_fw_chain *ch, struct table_config *tc)
tc->ta->change_ti(tc->astate, NULL);
}
struct swap_table_args {
int set;
int new_set;
int mv;
};
/*
* Change set for each matching table.
*
* Ensure we dispatch each table once by setting/checking ochange
* fields.
*/
static int
swap_table_set(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct table_config *tc;
struct swap_table_args *sta;
tc = (struct table_config *)no;
sta = (struct swap_table_args *)arg;
if (no->set != sta->set && (no->set != sta->new_set || sta->mv != 0))
return (0);
if (tc->ochanged != 0)
return (0);
tc->ochanged = 1;
ipfw_objhash_del(ni, no);
if (no->set == sta->set)
no->set = sta->new_set;
else
no->set = sta->set;
ipfw_objhash_add(ni, no);
return (0);
}
/*
* Cleans up ochange field for all tables.
*/
static int
clean_table_set_data(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct table_config *tc;
struct swap_table_args *sta;
tc = (struct table_config *)no;
sta = (struct swap_table_args *)arg;
tc->ochanged = 0;
return (0);
}
/*
* Swaps tables within two sets.
*/
void
ipfw_swap_tables_sets(struct ip_fw_chain *ch, uint32_t set,
uint32_t new_set, int mv)
{
struct swap_table_args sta;
IPFW_UH_WLOCK_ASSERT(ch);
sta.set = set;
sta.new_set = new_set;
sta.mv = mv;
ipfw_objhash_foreach(CHAIN_TO_NI(ch), swap_table_set, &sta);
ipfw_objhash_foreach(CHAIN_TO_NI(ch), clean_table_set_data, &sta);
}
/*
* Move all tables which are reference by rules in @rr to set @new_set.
* Makes sure that all relevant tables are referenced ONLLY by given rules.
*
* Returns 0 on success,
*/
int
ipfw_move_tables_sets(struct ip_fw_chain *ch, ipfw_range_tlv *rt,
uint32_t new_set)
{
struct ip_fw *rule;
struct table_config *tc;
struct named_object *no;
struct namedobj_instance *ni;
int bad, i, l, cmdlen;
uint16_t kidx;
ipfw_insn *cmd;
IPFW_UH_WLOCK_ASSERT(ch);
ni = CHAIN_TO_NI(ch);
/* Stage 1: count number of references by given rules */
for (i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (classify_opcode_kidx(cmd, &kidx) != 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
KASSERT(no != NULL,
("objhash lookup failed on index %d", kidx));
tc = (struct table_config *)no;
tc->ocount++;
}
}
/* Stage 2: verify "ownership" */
bad = 0;
for (i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (classify_opcode_kidx(cmd, &kidx) != 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
KASSERT(no != NULL,
("objhash lookup failed on index %d", kidx));
tc = (struct table_config *)no;
if (tc->no.refcnt != tc->ocount) {
/*
* Number of references differ:
* Other rule(s) are holding reference to given
* table, so it is not possible to change its set.
*
* Note that refcnt may account
* references to some going-to-be-added rules.
* Since we don't know their numbers (and event
* if they will be added) it is perfectly OK
* to return error here.
*/
bad = 1;
break;
}
}
if (bad != 0)
break;
}
/* Stage 3: change set or cleanup */
for (i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (classify_opcode_kidx(cmd, &kidx) != 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
KASSERT(no != NULL,
("objhash lookup failed on index %d", kidx));
tc = (struct table_config *)no;
tc->ocount = 0;
if (bad != 0)
continue;
/* Actually change set. */
ipfw_objhash_del(ni, no);
no->set = new_set;
ipfw_objhash_add(ni, no);
}
}
return (bad);
}
static struct ipfw_sopt_handler scodes[] = {
{ IP_FW_TABLE_XCREATE, 0, HDIR_SET, create_table },
{ IP_FW_TABLE_XDESTROY, 0, HDIR_SET, flush_table_v0 },