Kernel changes:

* Implement proper checks for switching between global and set-aware tables
* Split IP_FW_DEL mess into the following opcodes:
  * IP_FW_XDEL (del rules matching pattern)
  * IP_FW_XMOVE (move rules matching pattern to another set)
  * IP_FW_SET_SWAP (swap between 2 sets)
  * IP_FW_SET_MOVE (move one set to another one)
  * IP_FW_SET_ENABLE (enable/disable sets)
* Add IP_FW_XZERO / IP_FW_XRESETLOG to finish IP_FW3 migration.
* Use unified ipfw_range_tlv as range description for all of the above.
* Check dynamic states IFF there was non-zero number of deleted dyn rules,
* Del relevant dynamic states with singe traversal instead of per-rule one.

Userland changes:
* Switch ipfw(8) to use new opcodes.
This commit is contained in:
Alexander V. Chernikov 2014-08-07 21:37:31 +00:00
parent 46d5200874
commit a73d728d31
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/ipfw/; revision=269689
8 changed files with 901 additions and 244 deletions

View File

@ -2110,6 +2110,19 @@ show_dyn_state(struct cmdline_opts *co, struct format_opts *fo,
bprintf(bp, " UNKNOWN <-> UNKNOWN\n");
}
static int
do_range_cmd(int cmd, ipfw_range_tlv *rt)
{
ipfw_range_header rh;
memset(&rh, 0, sizeof(rh));
memcpy(&rh.range, rt, sizeof(*rt));
rh.range.head.length = sizeof(*rt);
rh.range.head.type = IPFW_TLV_RANGE;
return (do_set3(cmd, &rh.opheader, sizeof(rh)));
}
/*
* This one handles all set-related commands
* ipfw set { show | enable | disable }
@ -2122,12 +2135,13 @@ ipfw_sets_handler(char *av[])
{
uint32_t masks[2];
int i;
uint16_t rulenum;
uint8_t cmd, new_set;
uint8_t cmd, new_set, rulenum;
ipfw_range_tlv rt;
char *msg;
size_t size;
av++;
memset(&rt, 0, sizeof(rt));
if (av[0] == NULL)
errx(EX_USAGE, "set needs command");
@ -2156,33 +2170,38 @@ ipfw_sets_handler(char *av[])
av++;
if ( av[0] == NULL || av[1] == NULL )
errx(EX_USAGE, "set swap needs 2 set numbers\n");
rulenum = atoi(av[0]);
new_set = atoi(av[1]);
if (!isdigit(*(av[0])) || rulenum > RESVD_SET)
rt.set = atoi(av[0]);
rt.new_set = atoi(av[1]);
if (!isdigit(*(av[0])) || rt.set > RESVD_SET)
errx(EX_DATAERR, "invalid set number %s\n", av[0]);
if (!isdigit(*(av[1])) || new_set > RESVD_SET)
if (!isdigit(*(av[1])) || rt.new_set > RESVD_SET)
errx(EX_DATAERR, "invalid set number %s\n", av[1]);
masks[0] = (4 << 24) | (new_set << 16) | (rulenum);
i = do_cmd(IP_FW_DEL, masks, sizeof(uint32_t));
i = do_range_cmd(IP_FW_SET_SWAP, &rt);
} else if (_substrcmp(*av, "move") == 0) {
av++;
if (av[0] && _substrcmp(*av, "rule") == 0) {
cmd = 2;
rt.flags = IPFW_RCFLAG_RANGE; /* move rules to new set */
cmd = IP_FW_XMOVE;
av++;
} else
cmd = 3;
cmd = IP_FW_SET_MOVE; /* Move set to new one */
if (av[0] == NULL || av[1] == NULL || av[2] == NULL ||
av[3] != NULL || _substrcmp(av[1], "to") != 0)
errx(EX_USAGE, "syntax: set move [rule] X to Y\n");
rulenum = atoi(av[0]);
new_set = atoi(av[2]);
if (!isdigit(*(av[0])) || (cmd == 3 && rulenum > RESVD_SET) ||
(cmd == 2 && rulenum == IPFW_DEFAULT_RULE) )
rt.new_set = atoi(av[2]);
if (cmd == IP_FW_XMOVE) {
rt.start_rule = rulenum;
rt.end_rule = rulenum;
} else
rt.set = rulenum;
rt.new_set = atoi(av[2]);
if (!isdigit(*(av[0])) || (cmd == 3 && rt.set > RESVD_SET) ||
(cmd == 2 && rt.start_rule == IPFW_DEFAULT_RULE) )
errx(EX_DATAERR, "invalid source number %s\n", av[0]);
if (!isdigit(*(av[2])) || new_set > RESVD_SET)
errx(EX_DATAERR, "invalid dest. set %s\n", av[1]);
masks[0] = (cmd << 24) | (new_set << 16) | (rulenum);
i = do_cmd(IP_FW_DEL, masks, sizeof(uint32_t));
i = do_range_cmd(cmd, &rt);
} else if (_substrcmp(*av, "disable") == 0 ||
_substrcmp(*av, "enable") == 0 ) {
int which = _substrcmp(*av, "enable") == 0 ? 1 : 0;
@ -2210,9 +2229,11 @@ ipfw_sets_handler(char *av[])
errx(EX_DATAERR,
"cannot enable and disable the same set\n");
i = do_cmd(IP_FW_DEL, masks, sizeof(masks));
rt.set = masks[0];
rt.new_set = masks[1];
i = do_range_cmd(IP_FW_SET_ENABLE, &rt);
if (i)
warn("set enable/disable: setsockopt(IP_FW_DEL)");
warn("set enable/disable: setsockopt(IP_FW_SET_ENABLE)");
} else
errx(EX_USAGE, "invalid set command %s\n", *av);
}
@ -2984,9 +3005,11 @@ ipfw_delete(char *av[])
int i;
int exitval = EX_OK;
int do_set = 0;
ipfw_range_tlv rt;
av++;
NEED1("missing rule specification");
memset(&rt, 0, sizeof(rt));
if ( *av && _substrcmp(*av, "set") == 0) {
/* Do not allow using the following syntax:
* ipfw set N delete set M
@ -3009,15 +3032,25 @@ ipfw_delete(char *av[])
} else if (co.do_pipe) {
exitval = ipfw_delete_pipe(co.do_pipe, i);
} else {
if (co.use_set)
rulenum = (i & 0xffff) | (5 << 24) |
((co.use_set - 1) << 16);
else
rulenum = (i & 0xffff) | (do_set << 24);
i = do_cmd(IP_FW_DEL, &rulenum, sizeof rulenum);
if (i) {
if (do_set != 0) {
rt.set = i & 31;
rt.flags = IPFW_RCFLAG_SET;
} else {
rt.start_rule = i & 0xffff;
rt.end_rule = i & 0xffff;
if (rt.start_rule == 0 && rt.end_rule == 0)
rt.flags |= IPFW_RCFLAG_ALL;
else
rt.flags |= IPFW_RCFLAG_RANGE;
if (co.use_set != 0) {
rt.set = co.use_set - 1;
rt.flags |= IPFW_RCFLAG_SET;
}
}
i = do_range_cmd(IP_FW_XDEL, &rt);
if (i != 0) {
exitval = EX_UNAVAILABLE;
warn("rule %u: setsockopt(IP_FW_DEL)",
warn("rule %u: setsockopt(IP_FW_XDEL)",
rulenum);
}
}
@ -4681,25 +4714,31 @@ ipfw_add(char *av[])
/*
* clear the counters or the log counters.
* optname has the following values:
* 0 (zero both counters and logging)
* 1 (zero logging only)
*/
void
ipfw_zero(int ac, char *av[], int optname /* 0 = IP_FW_ZERO, 1 = IP_FW_RESETLOG */)
ipfw_zero(int ac, char *av[], int optname)
{
uint32_t arg, saved_arg;
ipfw_range_tlv rt;
uint32_t arg;
int failed = EX_OK;
char const *errstr;
char const *name = optname ? "RESETLOG" : "ZERO";
optname = optname ? IP_FW_RESETLOG : IP_FW_ZERO;
optname = optname ? IP_FW_XRESETLOG : IP_FW_XZERO;
memset(&rt, 0, sizeof(rt));
av++; ac--;
if (!ac) {
if (ac == 0) {
/* clear all entries */
if (do_cmd(optname, NULL, 0) < 0)
err(EX_UNAVAILABLE, "setsockopt(IP_FW_%s)", name);
rt.flags = IPFW_RCFLAG_ALL;
if (do_range_cmd(optname, &rt) < 0)
err(EX_UNAVAILABLE, "setsockopt(IP_FW_X%s)", name);
if (!co.do_quiet)
printf("%s.\n", optname == IP_FW_ZERO ?
printf("%s.\n", optname == IP_FW_XZERO ?
"Accounting cleared":"Logging counts reset");
return;
@ -4712,18 +4751,20 @@ ipfw_zero(int ac, char *av[], int optname /* 0 = IP_FW_ZERO, 1 = IP_FW_RESETLOG
if (errstr)
errx(EX_DATAERR,
"invalid rule number %s\n", *av);
saved_arg = arg;
if (co.use_set)
arg |= (1 << 24) | ((co.use_set - 1) << 16);
av++;
ac--;
if (do_cmd(optname, &arg, sizeof(arg))) {
warn("rule %u: setsockopt(IP_FW_%s)",
saved_arg, name);
rt.start_rule = arg;
rt.end_rule = arg;
rt.flags |= IPFW_RCFLAG_RANGE;
if (co.use_set != 0) {
rt.set = co.use_set - 1;
rt.flags |= IPFW_RCFLAG_SET;
}
if (do_range_cmd(optname, &rt) != 0) {
warn("rule %u: setsockopt(IP_FW_X%s)",
arg, name);
failed = EX_UNAVAILABLE;
} else if (!co.do_quiet)
printf("Entry %d %s.\n", saved_arg,
optname == IP_FW_ZERO ?
printf("Entry %d %s.\n", arg,
optname == IP_FW_XZERO ?
"cleared" : "logging count reset");
} else {
errx(EX_USAGE, "invalid rule number ``%s''", *av);
@ -4736,7 +4777,7 @@ ipfw_zero(int ac, char *av[], int optname /* 0 = IP_FW_ZERO, 1 = IP_FW_RESETLOG
void
ipfw_flush(int force)
{
int cmd = co.do_pipe ? IP_DUMMYNET_FLUSH : IP_FW_FLUSH;
ipfw_range_tlv rt;
if (!force && !co.do_quiet) { /* need to ask user */
int c;
@ -4758,13 +4799,14 @@ ipfw_flush(int force)
return;
}
/* `ipfw set N flush` - is the same that `ipfw delete set N` */
if (co.use_set) {
uint32_t arg = ((co.use_set - 1) & 0xffff) | (1 << 24);
if (do_cmd(IP_FW_DEL, &arg, sizeof(arg)) < 0)
err(EX_UNAVAILABLE, "setsockopt(IP_FW_DEL)");
} else if (do_cmd(cmd, NULL, 0) < 0)
err(EX_UNAVAILABLE, "setsockopt(IP_%s_FLUSH)",
co.do_pipe ? "DUMMYNET" : "FW");
memset(&rt, 0, sizeof(rt));
if (co.use_set != 0) {
rt.set = co.use_set - 1;
rt.flags = IPFW_RCFLAG_SET;
} else
rt.flags = IPFW_RCFLAG_ALL;
if (do_range_cmd(IP_FW_XDEL, &rt) != 0)
err(EX_UNAVAILABLE, "setsockopt(IP_FW_XDEL)");
if (!co.do_quiet)
printf("Flushed all %s.\n", co.do_pipe ? "pipes" : "rules");
}

View File

@ -86,11 +86,18 @@ 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 */
#define IP_FW_TABLE_XFIND 99 /* finds an entry */
#define IP_FW_XIFLIST 100 /* list tracked interfaces */
#define IP_FW_TABLES_ALIST 101 /* list table algorithms */
#define IP_FW_TABLE_XSWAP 102 /* swap two tables */
#define IP_FW_XADD 98 /* add rule */
#define IP_FW_XDEL 99 /* del rule */
#define IP_FW_XMOVE 100 /* move rules to different set */
#define IP_FW_XZERO 101 /* clear accounting */
#define IP_FW_XRESETLOG 102 /* zero rules logs */
#define IP_FW_SET_SWAP 103 /* Swap between 2 sets */
#define IP_FW_SET_MOVE 104 /* Move one set to another one */
#define IP_FW_SET_ENABLE 105 /* Enable/disable sets */
#define IP_FW_TABLE_XFIND 106 /* finds an entry */
#define IP_FW_XIFLIST 107 /* list tracked interfaces */
#define IP_FW_TABLES_ALIST 108 /* list table algorithms */
#define IP_FW_TABLE_XSWAP 109 /* swap two tables */
/*
* Usage guidelines:
@ -735,6 +742,7 @@ typedef struct _ipfw_obj_tlv {
#define IPFW_TLV_DYN_ENT 6
#define IPFW_TLV_RULE_ENT 7
#define IPFW_TLV_TBLENT_LIST 8
#define IPFW_TLV_RANGE 9
/* Object name TLV */
typedef struct _ipfw_obj_ntlv {
@ -799,6 +807,19 @@ typedef struct _ipfw_obj_ctlv {
uint8_t spare;
} ipfw_obj_ctlv;
/* Range TLV */
typedef struct _ipfw_range_tlv {
ipfw_obj_tlv head; /* TLV header */
uint32_t flags; /* Range flags */
uint16_t start_rule; /* Range start */
uint16_t end_rule; /* Range end */
uint32_t set; /* Range set to match */
uint32_t new_set; /* New set to move/swap to */
} ipfw_range_tlv;
#define IPFW_RCFLAG_RANGE 0x01 /* rule range is set */
#define IPFW_RCFLAG_ALL 0x02 /* match ALL rules */
#define IPFW_RCFLAG_SET 0x04 /* match rules in given set */
typedef struct _ipfw_ta_tinfo {
uint32_t flags; /* Format flags */
uint32_t spare;
@ -893,4 +914,9 @@ typedef struct _ipfw_cfg_lheader {
uint32_t end_rule;
} ipfw_cfg_lheader;
typedef struct _ipfw_range_header {
ip_fw3_opheader opheader; /* IP_FW3 opcode */
ipfw_range_tlv range;
} ipfw_range_header;
#endif /* _IPFW2_H */

View File

@ -158,6 +158,7 @@ ipfw_nat_cfg_t *ipfw_nat_get_log_ptr;
#ifdef SYSCTL_NODE
uint32_t dummy_def = IPFW_DEFAULT_RULE;
static int sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS);
static int sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS);
SYSBEGIN(f3)
@ -180,8 +181,8 @@ SYSCTL_UINT(_net_inet_ip_fw, OID_AUTO, default_rule, CTLFLAG_RD,
SYSCTL_VNET_PROC(_net_inet_ip_fw, OID_AUTO, tables_max,
CTLTYPE_UINT|CTLFLAG_RW, 0, 0, sysctl_ipfw_table_num, "IU",
"Maximum number of concurrently used tables");
SYSCTL_VNET_INT(_net_inet_ip_fw, OID_AUTO, tables_sets,
CTLFLAG_RW, &VNET_NAME(fw_tables_sets), 0,
SYSCTL_VNET_PROC(_net_inet_ip_fw, OID_AUTO, tables_sets,
CTLTYPE_UINT|CTLFLAG_RW, 0, 0, sysctl_ipfw_tables_sets, "IU",
"Use per-set namespace for tables");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, default_to_accept, CTLFLAG_RDTUN,
&default_to_accept, 0,
@ -2569,7 +2570,27 @@ sysctl_ipfw_table_num(SYSCTL_HANDLER_ARGS)
return (ipfw_resize_tables(&V_layer3_chain, ntables));
}
/*
* Switches table namespace between global and per-set.
*/
static int
sysctl_ipfw_tables_sets(SYSCTL_HANDLER_ARGS)
{
int error;
unsigned int sets;
sets = V_fw_tables_sets;
error = sysctl_handle_int(oidp, &sets, 0, req);
/* Read operation or some error */
if ((error != 0) || (req->newptr == NULL))
return (error);
return (ipfw_switch_tables_namespace(&V_layer3_chain, sets));
}
#endif
/*
* Module and VNET glue
*/
@ -2752,8 +2773,7 @@ vnet_ipfw_uninit(const void *unused)
rule->x_next = reap;
reap = rule;
}
if (chain->map)
free(chain->map, M_IPFW);
free(chain->map, M_IPFW);
ipfw_destroy_skipto_cache(chain);
IPFW_WUNLOCK(chain);
IPFW_UH_WUNLOCK(chain);

View File

@ -196,8 +196,7 @@ static int ipfw_dyn_count; /* number of objects */
static int last_log; /* Log ratelimiting */
static void ipfw_dyn_tick(void *vnetx);
static void check_dyn_rules(struct ip_fw_chain *, struct ip_fw *,
int, int, int);
static void check_dyn_rules(struct ip_fw_chain *, ipfw_range_tlv *, int, int);
#ifdef SYSCTL_NODE
static int sysctl_ipfw_dyn_count(SYSCTL_HANDLER_ARGS);
@ -1008,7 +1007,7 @@ ipfw_dyn_tick(void * vnetx)
check_ka = 1;
}
check_dyn_rules(chain, NULL, RESVD_SET, check_ka, 1);
check_dyn_rules(chain, NULL, check_ka, 1);
callout_reset_on(&V_ipfw_timeout, hz, ipfw_dyn_tick, vnetx, 0);
@ -1040,8 +1039,8 @@ ipfw_dyn_tick(void * vnetx)
* are not freed by other instance (see stage 2, 3)
*/
static void
check_dyn_rules(struct ip_fw_chain *chain, struct ip_fw *rule,
int set, int check_ka, int timer)
check_dyn_rules(struct ip_fw_chain *chain, ipfw_range_tlv *rt,
int check_ka, int timer)
{
struct mbuf *m0, *m, *mnext, **mtailp;
struct ip *h;
@ -1105,12 +1104,10 @@ check_dyn_rules(struct ip_fw_chain *chain, struct ip_fw *rule,
/*
* Remove rules which are:
* 1) expired
* 2) created by given rule
* 3) created by any rule in given set
* 2) matches deletion range
*/
if ((TIME_LEQ(q->expire, time_uptime)) ||
((rule != NULL) && (q->rule == rule)) ||
((set != RESVD_SET) && (q->rule->set == set))) {
(rt != NULL && ipfw_match_range(q->rule, rt))) {
if (TIME_LE(time_uptime, q->expire) &&
q->dyn_type == O_KEEP_STATE &&
V_dyn_keep_states != 0) {
@ -1324,8 +1321,7 @@ check_dyn_rules(struct ip_fw_chain *chain, struct ip_fw *rule,
* Deletes all dynamic rules originated by given rule or all rules in
* given set. Specify RESVD_SET to indicate set should not be used.
* @chain - pointer to current ipfw rules chain
* @rule - delete all states originated by given rule if != NULL
* @set - delete all states originated by any rule in set @set if != RESVD_SET
* @rr - delete all states originated by rules in matched range.
*
* Function has to be called with IPFW_UH_WLOCK held.
* Additionally, function assume that dynamic rule/set is
@ -1333,10 +1329,39 @@ check_dyn_rules(struct ip_fw_chain *chain, struct ip_fw *rule,
* 'deleted' rules.
*/
void
ipfw_expire_dyn_rules(struct ip_fw_chain *chain, struct ip_fw *rule, int set)
ipfw_expire_dyn_rules(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
{
check_dyn_rules(chain, rule, set, 0, 0);
check_dyn_rules(chain, rt, 0, 0);
}
/*
* Check if rule contains at least one dynamic opcode.
*
* Returns 1 if such opcode is found, 0 otherwise.
*/
int
ipfw_is_dyn_rule(struct ip_fw *rule)
{
int cmdlen, l;
ipfw_insn *cmd;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
switch (cmd->opcode) {
case O_LIMIT:
case O_KEEP_STATE:
case O_PROBE_STATE:
case O_CHECK_STATE:
return (1);
}
}
return (0);
}
void

View File

@ -177,7 +177,8 @@ enum { /* result for matching dynamic rules */
*/
struct ip_fw_chain;
struct sockopt_data;
void ipfw_expire_dyn_rules(struct ip_fw_chain *, struct ip_fw *, int);
int ipfw_is_dyn_rule(struct ip_fw *rule);
void ipfw_expire_dyn_rules(struct ip_fw_chain *, ipfw_range_tlv *);
void ipfw_dyn_unlock(ipfw_dyn_rule *q);
struct tcphdr;
@ -272,7 +273,6 @@ struct ip_fw_chain {
#endif
int static_len; /* total len of static rules (v0) */
uint32_t gencnt; /* NAT generation count */
struct ip_fw *reap; /* list of rules to reap */
struct ip_fw *default_rule;
struct tables_config *tblcfg; /* tables module data */
void *ifcfg; /* interface module data */
@ -507,6 +507,7 @@ void ipfw_reap_rules(struct ip_fw *head);
void ipfw_init_counters(void);
void ipfw_destroy_counters(void);
struct ip_fw *ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize);
int ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt);
caddr_t ipfw_get_sopt_space(struct sockopt_data *sd, size_t needed);
caddr_t ipfw_get_sopt_header(struct sockopt_data *sd, size_t needed);
@ -547,6 +548,7 @@ int ipfw_lookup_table_extended(struct ip_fw_chain *ch, uint16_t tbl, uint16_t pl
void *paddr, uint32_t *val);
int ipfw_init_tables(struct ip_fw_chain *ch);
int ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables);
int ipfw_switch_tables_namespace(struct ip_fw_chain *ch, unsigned int nsets);
void ipfw_destroy_tables(struct ip_fw_chain *ch);
/* In ip_fw_nat.c -- XXX to be moved to ip_var.h */

View File

@ -305,11 +305,12 @@ get_map(struct ip_fw_chain *chain, int extra, int locked)
for (;;) {
struct ip_fw **map;
int i;
int i, mflags;
mflags = M_ZERO | ((locked != 0) ? M_NOWAIT : M_WAITOK);
i = chain->n_rules + extra;
map = malloc(i * sizeof(struct ip_fw *), M_IPFW,
locked ? M_NOWAIT : M_WAITOK);
map = malloc(i * sizeof(struct ip_fw *), M_IPFW, mflags);
if (map == NULL) {
printf("%s: cannot allocate map\n", __FUNCTION__);
return NULL;
@ -623,16 +624,6 @@ ipfw_reap_rules(struct ip_fw *head)
}
/*
* Used by del_entry() to check if a rule should be kept.
* Returns 1 if the rule must be kept, 0 otherwise.
*
* Called with cmd = {0,1,5}.
* cmd == 0 matches on rule numbers, excludes rules in RESVD_SET if n == 0 ;
* cmd == 1 matches on set numbers only, rule numbers are ignored;
* cmd == 5 matches on rule and set numbers.
*
* n == 0 is a wildcard for rule numbers, there is no wildcard for sets.
*
* Rules to keep are
* (default || reserved || !match_set || !match_number)
* where
@ -649,14 +640,386 @@ ipfw_reap_rules(struct ip_fw *head)
* // number is ignored for cmd == 1 or n == 0
*
*/
static int
keep_rule(struct ip_fw *rule, uint8_t cmd, uint8_t set, uint32_t n)
int
ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt)
{
return
(rule->rulenum == IPFW_DEFAULT_RULE) ||
(cmd == 0 && n == 0 && rule->set == RESVD_SET) ||
!(cmd == 0 || rule->set == set) ||
!(cmd == 1 || n == 0 || n == rule->rulenum);
/* Don't match default rule regardless of query */
if (rule->rulenum == IPFW_DEFAULT_RULE)
return (0);
/* Don't match rules in reserved set for flush requests */
if ((rt->flags & IPFW_RCFLAG_ALL) != 0 && rule->set == RESVD_SET)
return (0);
/* If we're filtering by set, don't match other sets */
if ((rt->flags & IPFW_RCFLAG_SET) != 0 && rule->set != rt->set)
return (0);
if ((rt->flags & IPFW_RCFLAG_RANGE) != 0 &&
(rule->rulenum < rt->start_rule || rule->rulenum > rt->end_rule))
return (0);
return (1);
}
/*
* Delete rules matching range @rt.
* Saves number of deleted rules in @ndel.
*
* Returns 0 on success.
*/
static int
delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel)
{
struct ip_fw *reap, *rule, **map;
int end, start;
int i, n, ndyn, ofs;
reap = NULL;
IPFW_UH_WLOCK(chain); /* arbitrate writers */
/*
* Stage 1: Determine range to inspect.
* Range is half-inclusive, e.g [start, end).
*/
start = 0;
end = chain->n_rules - 1;
if ((rt->flags & IPFW_RCFLAG_RANGE) != 0) {
start = ipfw_find_rule(chain, rt->start_rule, 0);
end = ipfw_find_rule(chain, rt->end_rule, 0);
if (rt->end_rule != IPFW_DEFAULT_RULE)
while (chain->map[end]->rulenum == rt->end_rule)
end++;
}
/* Allocate new map of the same size */
map = get_map(chain, 0, 1 /* locked */);
if (map == NULL) {
IPFW_UH_WUNLOCK(chain);
return (ENOMEM);
}
n = 0;
ndyn = 0;
ofs = start;
/* 1. bcopy the initial part of the map */
if (start > 0)
bcopy(chain->map, map, start * sizeof(struct ip_fw *));
/* 2. copy active rules between start and end */
for (i = start; i < end; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0) {
map[ofs++] = rule;
continue;
}
n++;
if (ipfw_is_dyn_rule(rule) != 0)
ndyn++;
}
/* 3. copy the final part of the map */
bcopy(chain->map + end, map + ofs,
(chain->n_rules - end) * sizeof(struct ip_fw *));
/* 4. recalculate skipto cache */
update_skipto_cache(chain, map);
/* 5. swap the maps (under UH_WLOCK + WHLOCK) */
map = swap_map(chain, map, chain->n_rules - n);
/* 6. Remove all dynamic states originated by deleted rules */
if (ndyn > 0)
ipfw_expire_dyn_rules(chain, rt);
/* 7. now remove the rules deleted from the old map */
for (i = start; i < end; i++) {
rule = map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
chain->static_len -= RULEUSIZE0(rule);
rule->x_next = reap;
reap = rule;
}
ipfw_unbind_table_list(chain, reap);
IPFW_UH_WUNLOCK(chain);
ipfw_reap_rules(reap);
if (map != NULL)
free(map, M_IPFW);
*ndel = n;
return (0);
}
/*
* Changes set of given rule rannge @rt
* with each other.
*
* Returns 0 on success.
*/
static int
move_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
{
struct ip_fw *rule;
int i;
IPFW_UH_WLOCK(chain);
/*
* Move rules with matching paramenerts to a new set.
* This one is much more complex. We have to ensure
* that all referenced tables (if any) are referenced
* 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);
}
}
/* XXX: We have to do swap holding WLOCK */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
rule->set = rt->new_set;
}
IPFW_UH_WUNLOCK(chain);
return (0);
}
/*
* Clear counters for a specific rule.
* Normally run under IPFW_UH_RLOCK, but these are idempotent ops
* so we only care that rules do not disappear.
*/
static void
clear_counters(struct ip_fw *rule, int log_only)
{
ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule);
if (log_only == 0)
IPFW_ZERO_RULE_COUNTER(rule);
if (l->o.opcode == O_LOG)
l->log_left = l->max_log;
}
/*
* Flushes rules counters and/or log values on matching range.
*
* Returns number of items cleared.
*/
static int
clear_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int log_only)
{
struct ip_fw *rule;
int num;
int i;
num = 0;
IPFW_UH_WLOCK(chain); /* arbitrate writers */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
clear_counters(rule, log_only);
num++;
}
IPFW_UH_WUNLOCK(chain);
return (num);
}
static int
check_range_tlv(ipfw_range_tlv *rt)
{
if (rt->head.length != sizeof(*rt))
return (1);
if (rt->start_rule > rt->end_rule)
return (1);
if (rt->set >= IPFW_MAX_SETS || rt->new_set >= IPFW_MAX_SETS)
return (1);
return (0);
}
/*
* Delete rules matching specified parameters
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ipfw_range_tlv ]
* Reply: [ ipfw_obj_header ipfw_range_tlv ]
*
* Saves number of deleted rules in ipfw_range_tlv->new_set.
*
* Returns 0 on success.
*/
static int
del_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int error, ndel;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
ndel = 0;
if ((error = delete_range(chain, &rh->range, &ndel)) != 0)
return (error);
/* Save number of rules deleted */
rh->range.new_set = ndel;
return (0);
}
/*
* Move rules/sets matching specified parameters
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ipfw_range_tlv ]
*
* Returns 0 on success.
*/
static int
move_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
return (move_range(chain, &rh->range));
}
/*
* Clear rule accounting data matching specified parameters
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ipfw_range_tlv ]
* Reply: [ ipfw_obj_header ipfw_range_tlv ]
*
* Saves number of cleared rules in ipfw_range_tlv->new_set.
*
* Returns 0 on success.
*/
static int
clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int log_only, num;
char *msg;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
log_only = (op3->opcode == IP_FW_XRESETLOG);
num = clear_range(chain, &rh->range, log_only);
if (rh->range.flags & IPFW_RCFLAG_ALL)
msg = log_only ? "All logging counts reset" :
"Accounting cleared";
else
msg = log_only ? "logging count reset" : "cleared";
if (V_fw_verbose) {
int lev = LOG_SECURITY | LOG_NOTICE;
log(lev, "ipfw: %s.\n", msg);
}
/* Save number of rules cleared */
rh->range.new_set = num;
return (0);
}
static void
enable_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
{
uint32_t v_set;
IPFW_UH_WLOCK_ASSERT(chain);
/* Change enabled/disabled sets mask */
v_set = (V_set_disable | rt->set) & ~rt->new_set;
v_set &= ~(1 << RESVD_SET); /* set RESVD_SET always enabled */
IPFW_WLOCK(chain);
V_set_disable = v_set;
IPFW_WUNLOCK(chain);
}
static void
swap_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int mv)
{
struct ip_fw *rule;
int i;
IPFW_UH_WLOCK_ASSERT(chain);
/* 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 (V_fw_tables_sets != 0)
ipfw_swap_tables_sets(chain, rt->set, rt->new_set, mv);
}
/*
* Swaps or moves set
* Data layout (v0)(current):
* Request: [ ipfw_obj_header ipfw_range_tlv ]
*
* Returns 0 on success.
*/
static int
manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (rh->range.head.length != sizeof(ipfw_range_tlv))
return (1);
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);
break;
case IP_FW_SET_ENABLE:
enable_sets(chain, &rh->range);
break;
}
IPFW_UH_WUNLOCK(chain);
return (0);
}
/**
@ -676,12 +1039,11 @@ keep_rule(struct ip_fw *rule, uint8_t cmd, uint8_t set, uint32_t n)
static int
del_entry(struct ip_fw_chain *chain, uint32_t arg)
{
struct ip_fw *rule;
uint32_t num; /* rule number or old_set */
uint8_t cmd, new_set;
int start, end, i, ofs, n;
struct ip_fw **map = NULL;
int do_del, ndel;
int error = 0;
ipfw_range_tlv rt;
num = arg & 0xffff;
cmd = (arg >> 24) & 0xff;
@ -697,151 +1059,60 @@ del_entry(struct ip_fw_chain *chain, uint32_t arg)
return EINVAL;
}
IPFW_UH_WLOCK(chain); /* arbitrate writers */
chain->reap = NULL; /* prepare for deletions */
/* Convert old requests into new representation */
memset(&rt, 0, sizeof(rt));
rt.start_rule = num;
rt.end_rule = num;
rt.set = num;
rt.new_set = new_set;
do_del = 0;
switch (cmd) {
case 0: /* delete rules "num" (num == 0 matches all) */
case 1: /* delete all rules in set N */
case 5: /* delete rules with number N and set "new_set". */
/*
* Locate first rule to delete (start), the rule after
* the last one to delete (end), and count how many
* rules to delete (n). Always use keep_rule() to
* determine which rules to keep.
*/
n = 0;
if (cmd == 1) {
/* look for a specific set including RESVD_SET.
* Must scan the entire range, ignore num.
*/
new_set = num;
for (start = -1, end = i = 0; i < chain->n_rules; i++) {
if (keep_rule(chain->map[i], cmd, new_set, 0))
continue;
if (start < 0)
start = i;
end = i;
n++;
}
end++; /* first non-matching */
} else {
/* Optimized search on rule numbers */
start = ipfw_find_rule(chain, num, 0);
for (end = start; end < chain->n_rules; end++) {
rule = chain->map[end];
if (num > 0 && rule->rulenum != num)
break;
if (!keep_rule(rule, cmd, new_set, num))
n++;
}
}
if (n == 0) {
/* A flush request (arg == 0 or cmd == 1) on empty
* ruleset returns with no error. On the contrary,
* if there is no match on a specific request,
* we return EINVAL.
*/
if (arg != 0 && cmd != 1)
error = EINVAL;
break;
}
/* We have something to delete. Allocate the new map */
map = get_map(chain, -n, 1 /* locked */);
if (map == NULL) {
error = EINVAL;
break;
}
/* 1. bcopy the initial part of the map */
if (start > 0)
bcopy(chain->map, map, start * sizeof(struct ip_fw *));
/* 2. copy active rules between start and end */
for (i = ofs = start; i < end; i++) {
rule = chain->map[i];
if (keep_rule(rule, cmd, new_set, num))
map[ofs++] = rule;
}
/* 3. copy the final part of the map */
bcopy(chain->map + end, map + ofs,
(chain->n_rules - end) * sizeof(struct ip_fw *));
/* 3.5. recalculate skipto cache */
update_skipto_cache(chain, map);
/* 4. swap the maps (under UH_WLOCK + WHLOCK) */
map = swap_map(chain, map, chain->n_rules - n);
/* 5. now remove the rules deleted from the old map */
if (cmd == 1)
ipfw_expire_dyn_rules(chain, NULL, new_set);
for (i = start; i < end; i++) {
rule = map[i];
if (keep_rule(rule, cmd, new_set, num))
continue;
chain->static_len -= RULEUSIZE0(rule);
if (cmd != 1)
ipfw_expire_dyn_rules(chain, rule, RESVD_SET);
rule->x_next = chain->reap;
chain->reap = rule;
}
case 0: /* delete rules numbered "rulenum" */
if (num == 0)
rt.flags |= IPFW_RCFLAG_ALL;
else
rt.flags |= IPFW_RCFLAG_RANGE;
do_del = 1;
break;
/*
* In the next 3 cases the loop stops at (n_rules - 1)
* because the default rule is never eligible..
*/
case 2: /* move rules with given RULE number to new set */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (rule->rulenum == num)
rule->set = new_set;
}
case 1: /* delete rules in set "rulenum" */
rt.flags |= IPFW_RCFLAG_SET;
do_del = 1;
break;
case 3: /* move rules with given SET number to new set */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (rule->set == num)
rule->set = new_set;
}
case 5: /* delete rules "rulenum" and set "new_set" */
rt.flags |= IPFW_RCFLAG_RANGE | IPFW_RCFLAG_SET;
rt.set = new_set;
rt.new_set = 0;
do_del = 1;
break;
case 4: /* swap two sets */
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (rule->set == num)
rule->set = new_set;
else if (rule->set == new_set)
rule->set = num;
}
case 2: /* move rules "rulenum" to set "new_set" */
rt.flags |= IPFW_RCFLAG_RANGE;
break;
case 3: /* move rules from set "rulenum" to set "new_set" */
IPFW_UH_WLOCK(chain);
swap_sets(chain, &rt, 1);
IPFW_UH_WUNLOCK(chain);
return (0);
case 4: /* swap sets "rulenum" and "new_set" */
IPFW_UH_WLOCK(chain);
swap_sets(chain, &rt, 0);
IPFW_UH_WUNLOCK(chain);
return (0);
default:
return (ENOTSUP);
}
rule = chain->reap;
chain->reap = NULL;
ipfw_unbind_table_list(chain, rule);
IPFW_UH_WUNLOCK(chain);
ipfw_reap_rules(rule);
if (map)
free(map, M_IPFW);
return error;
}
/*
* Clear counters for a specific rule.
* Normally run under IPFW_UH_RLOCK, but these are idempotent ops
* so we only care that rules do not disappear.
*/
static void
clear_counters(struct ip_fw *rule, int log_only)
{
ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule);
if (do_del != 0) {
if ((error = delete_range(chain, &rt, &ndel)) != 0)
return (error);
if (log_only == 0)
IPFW_ZERO_RULE_COUNTER(rule);
if (l->o.opcode == O_LOG)
l->log_left = l->max_log;
if (ndel == 0 && (cmd != 1 && num != 0))
return (EINVAL);
return (0);
}
return (move_range(chain, &rt));
}
/**
@ -1654,7 +1925,8 @@ check_object_name(ipfw_obj_ntlv *ntlv)
* Returns 0 on success.
*/
static int
add_entry(struct ip_fw_chain *chain, struct sockopt_data *sd)
add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_ctlv *ctlv, *rtlv, *tstate;
ipfw_obj_ntlv *ntlv;
@ -1662,7 +1934,6 @@ add_entry(struct ip_fw_chain *chain, struct sockopt_data *sd)
uint32_t count, read;
struct ip_fw_rule *r;
struct rule_check_info rci, *ci, *cbuf;
ip_fw3_opheader *op3;
int i, rsize;
if (sd->valsize > IP_FW3_READBUF)
@ -1870,8 +2141,8 @@ ipfw_ctl3(struct sockopt *sopt)
* Disallow modifications in really-really secure mode, but still allow
* the logging counters to be reset.
*/
if (opt == IP_FW_ADD ||
(sopt->sopt_dir == SOPT_SET && opt != IP_FW_RESETLOG)) {
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);
@ -1928,13 +2199,33 @@ ipfw_ctl3(struct sockopt *sopt)
error = dump_config(chain, &sdata);
break;
case IP_FW_XADD:
error = add_rules(chain, op3, &sdata);
break;
case IP_FW_XDEL:
error = del_rules(chain, op3, &sdata);
break;
case IP_FW_XZERO:
case IP_FW_XRESETLOG:
error = clear_rules(chain, op3, &sdata);
break;
case IP_FW_XMOVE:
error = move_rules(chain, op3, &sdata);
break;
case IP_FW_SET_SWAP:
case IP_FW_SET_MOVE:
case IP_FW_SET_ENABLE:
error = manage_sets(chain, op3, &sdata);
break;
case IP_FW_XIFLIST:
error = ipfw_list_ifaces(chain, &sdata);
break;
case IP_FW_XADD:
error = add_entry(chain, &sdata);
break;
/*--- TABLE opcodes ---*/
case IP_FW_TABLE_XCREATE:
error = ipfw_create_table(chain, op3, &sdata);

View File

@ -76,9 +76,11 @@ struct table_config {
uint8_t vtype; /* format table type */
uint8_t linked; /* 1 if already linked */
uint8_t tflags; /* type flags */
uint8_t spare;
uint8_t ochanged; /* used by set swapping */
uint32_t count; /* Number of records */
uint32_t limit; /* Max number of records */
uint32_t ocount; /* used by set swapping */
uint64_t gencnt; /* generation count */
char tablename[64]; /* table name */
struct table_algo *ta; /* Callbacks for given algo */
void *astate; /* algorithm state */
@ -117,7 +119,7 @@ static int ipfw_manage_table_ent_v0(struct ip_fw_chain *ch, ip_fw3_opheader *op3
struct sockopt_data *sd);
static int ipfw_manage_table_ent_v1(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd);
static int swap_table(struct ip_fw_chain *ch, struct tid_info *a,
static int swap_tables(struct ip_fw_chain *ch, struct tid_info *a,
struct tid_info *b);
static int check_table_space(struct ip_fw_chain *ch, struct table_config *tc,
@ -129,6 +131,7 @@ static struct table_algo *find_table_algo(struct tables_config *tableconf,
static void objheader_to_ti(struct _ipfw_obj_header *oh, struct tid_info *ti);
static void ntlv_to_ti(struct _ipfw_obj_ntlv *ntlv, struct tid_info *ti);
static int classify_table_opcode(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype);
#define CHAIN_TO_TCFG(chain) ((struct tables_config *)(chain)->tblcfg)
#define CHAIN_TO_NI(chain) (CHAIN_TO_TCFG(chain)->namehash)
@ -774,13 +777,13 @@ ipfw_swap_table(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
ntlv_to_ti(&oh->ntlv, &ti_a);
ntlv_to_ti((ipfw_obj_ntlv *)(oh + 1), &ti_b);
error = swap_table(ch, &ti_a, &ti_b);
error = swap_tables(ch, &ti_a, &ti_b);
return (error);
}
static int
swap_table(struct ip_fw_chain *ch, struct tid_info *a,
swap_tables(struct ip_fw_chain *ch, struct tid_info *a,
struct tid_info *b)
{
struct namedobj_instance *ni;
@ -1025,6 +1028,61 @@ ipfw_resize_tables(struct ip_fw_chain *ch, unsigned int ntables)
return (0);
}
/*
* Switch between "set 0" and "rule 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;
uint8_t type;
IPFW_UH_WLOCK(ch);
if (V_fw_tables_sets == sets) {
IPFW_UH_WUNLOCK(ch);
return (0);
}
ni = CHAIN_TO_NI(ch);
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_table_opcode(cmd, &kidx, &type) != 0)
continue;
no = ipfw_objhash_lookup_kidx(ni, kidx);
if (no->set != 0 || rule->set != 0) {
IPFW_UH_WUNLOCK(ch);
return (EBUSY);
}
}
}
V_fw_tables_sets = sets;
IPFW_UH_WUNLOCK(ch);
return (0);
}
int
ipfw_lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
uint32_t *val)
@ -2302,6 +2360,195 @@ bind_table_rule(struct ip_fw_chain *ch, struct ip_fw *rule,
return (error);
}
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 void
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;
if (tc->ochanged != 0)
return;
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);
}
/*
* Cleans up ochange field for all tables.
*/
static void
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;
}
/*
* 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.
*
* Retuns 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;
uint8_t type;
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_table_opcode(cmd, &kidx, &type) != 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_table_opcode(cmd, &kidx, &type) != 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_table_opcode(cmd, &kidx, &type) != 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);
}
/*
* Compatibility function for old ipfw(8) binaries.
* Rewrites table kernel indices with userland ones.

View File

@ -174,6 +174,10 @@ void ipfw_unbind_table_list(struct ip_fw_chain *chain, struct ip_fw *head);
/* utility functions */
int ipfw_check_table_name(char *name);
int ipfw_move_tables_sets(struct ip_fw_chain *ch, ipfw_range_tlv *rt,
uint32_t new_set);
void ipfw_swap_tables_sets(struct ip_fw_chain *ch, uint32_t old_set,
uint32_t new_set, int mv);
/* Legacy interfaces */
int ipfw_count_table(struct ip_fw_chain *ch, struct tid_info *ti,