diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 119d7b4daf5a..3ba52f4bfaf9 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -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"); } diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 65b226b7535d..33962cede074 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -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 */ diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index d2034e861adc..b47c8e30f71f 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -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); diff --git a/sys/netpfil/ipfw/ip_fw_dynamic.c b/sys/netpfil/ipfw/ip_fw_dynamic.c index 7cb052562931..475ad8e705f9 100644 --- a/sys/netpfil/ipfw/ip_fw_dynamic.c +++ b/sys/netpfil/ipfw/ip_fw_dynamic.c @@ -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 diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index 70558e2ea909..3b9d2f938b62 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -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 */ diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index 70c90e1b7c73..1d7f7686c967 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -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); diff --git a/sys/netpfil/ipfw/ip_fw_table.c b/sys/netpfil/ipfw/ip_fw_table.c index 2b02285cd162..f5d76f705615 100644 --- a/sys/netpfil/ipfw/ip_fw_table.c +++ b/sys/netpfil/ipfw/ip_fw_table.c @@ -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. diff --git a/sys/netpfil/ipfw/ip_fw_table.h b/sys/netpfil/ipfw/ip_fw_table.h index 8fab52f5a492..0a2308127abe 100644 --- a/sys/netpfil/ipfw/ip_fw_table.h +++ b/sys/netpfil/ipfw/ip_fw_table.h @@ -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,