One bugfix and one new feature.

The bugfix (ipfw2.c) makes the handling of port numbers with
a dash in the name, e.g. ftp-data, consistent with old ipfw:
use \\ before the - to consider it as part of the name and not
a range separator.

The new feature (all this description will go in the manpage):

each rule now belongs to one of 32 different sets, which can
be optionally specified in the following form:

	ipfw add 100 set 23 allow ip from any to any

If "set N" is not specified, the rule belongs to set 0.

Individual sets can be disabled, enabled, and deleted with the commands:

	ipfw disable set N
	ipfw enable set N
	ipfw delete set N

Enabling/disabling of a set is atomic. Rules belonging to a disabled
set are skipped during packet matching, and they are not listed
unless you use the '-S' flag in the show/list commands.
Note that dynamic rules, once created, are always active until
they expire or their parent rule is deleted.
Set 31 is reserved for the default rule and cannot be disabled.

All sets are enabled by default. The enable/disable status of the sets
can be shown with the command

	ipfw show sets

Hopefully, this feature will make life easier to those who want to
have atomic ruleset addition/deletion/tests. Examples:

To add a set of rules atomically:

	ipfw disable set 18
	ipfw add ... set 18 ...		# repeat as needed
	ipfw enable set 18

To delete a set of rules atomically

	ipfw disable set 18
	ipfw delete set 18
	ipfw enable set 18

To test a ruleset and disable it and regain control if something
goes wrong:

	ipfw disable set 18
	ipfw add ... set 18 ...         # repeat as needed
	ipfw enable set 18 ; echo "done "; sleep 30 && ipfw disable set 18

    here if everything goes well, you press control-C before
    the "sleep" terminates, and your ruleset will be left
    active. Otherwise, e.g. if you cannot access your box,
    the ruleset will be disabled after the sleep terminates.

I think there is only one more thing that one might want, namely
a command to assign all rules in set X to set Y, so one can
test a ruleset using the above mechanisms, and once it is
considered acceptable, make it part of an existing ruleset.
This commit is contained in:
luigi 2002-08-10 04:37:32 +00:00
parent 1627a3b4be
commit e3c4c6c9da
3 changed files with 183 additions and 43 deletions

View File

@ -65,6 +65,7 @@ int s, /* main RAW socket */
do_sort, /* field to sort results (0 = no) */
do_dynamic, /* display dynamic rules */
do_expired, /* display expired dynamic rules */
show_sets, /* display rule sets */
verbose;
#define IP_MASK_ALL 0xffffffff
@ -410,34 +411,45 @@ print_newports(ipfw_insn_u16 *cmd, int proto)
* proto == -1 disables the protocol check;
* proto == IPPROTO_ETHERTYPE looks up an internal table
* proto == <some value in /etc/protocols> matches the values there.
* Returns *end == s in case the parameter is not found.
*/
static int
strtoport(char *s, char **end, int base, int proto)
{
char *s1, sep;
char *p, *buf;
char *s1;
int i;
*end = s; /* default - not found */
if ( *s == '\0')
goto none;
return 0; /* not found */
if (isdigit(*s))
return strtol(s, end, base);
/*
* find separator and replace with a '\0'
* find separator. '\\' escapes the next char.
*/
for (s1 = s; *s1 && isalnum(*s1) ; s1++)
;
sep = *s1;
*s1 = '\0';
for (s1 = s; *s1 && (isalnum(*s1) || *s1 == '\\') ; s1++)
if (*s1 == '\\' && s1[1] != '\0')
s1++;
buf = malloc(s1 - s + 1);
if (buf == NULL)
return 0;
/*
* copy into a buffer skipping backslashes
*/
for (p = s, i = 0; p != s1 ; p++)
if ( *p != '\\')
buf[i++] = *p;
buf[i++] = '\0';
if (proto == IPPROTO_ETHERTYPE) {
i = match_token(ether_types, s);
*s1 = sep;
if (i == -1) { /* not found */
*end = s;
return 0;
} else {
i = match_token(ether_types, buf);
free(buf);
if (i != -1) { /* found */
*end = s1;
return i;
}
@ -448,16 +460,14 @@ strtoport(char *s, char **end, int base, int proto)
if (proto != 0)
pe = getprotobynumber(proto);
setservent(1);
se = getservbyname(s, pe ? pe->p_name : NULL);
*s1 = sep;
se = getservbyname(buf, pe ? pe->p_name : NULL);
free(buf);
if (se != NULL) {
*end = s1;
return ntohs(se->s_port);
}
}
none:
*end = s;
return 0;
return 0; /* not found */
}
/*
@ -749,6 +759,14 @@ show_ipfw(struct ip_fw *rule)
ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */
int or_block = 0; /* we are in an or block */
u_int32_t set_disable = (u_int32_t)(rule->next_rule);
if (set_disable & (1 << rule->set)) { /* disabled */
if (!show_sets)
return;
else
printf("# DISABLED ");
}
printf("%05u ", rule->rulenum);
if (do_acct)
@ -767,6 +785,9 @@ show_ipfw(struct ip_fw *rule)
}
}
if (show_sets)
printf("set %d ", rule->set);
/*
* first print actions
*/
@ -1332,6 +1353,23 @@ list(int ac, char *av[])
ac--;
av++;
if (ac && !strncmp(*av, "sets", strlen(*av)) ) {
int i;
u_int32_t set_disable;
nbytes = sizeof(struct ip_fw);
if ((data = malloc(nbytes)) == NULL)
err(EX_OSERR, "malloc");
if (getsockopt(s, IPPROTO_IP, IP_FW_GET, data, &nbytes) < 0)
err(EX_OSERR, "getsockopt(IP_FW_GET)");
set_disable = (u_int32_t)(((struct ip_fw *)data)->next_rule);
for (i = 0; i < 31; i++)
printf("%s set %d\n",
set_disable & (1<<i) ? "disable" : "enable", i);
return;
}
/* get rules or pipes from kernel, resizing array as necessary */
nbytes = nalloc;
@ -1649,10 +1687,21 @@ delete(int ac, char *av[])
struct dn_pipe pipe;
int i;
int exitval = EX_OK;
int do_set = 0;
memset(&pipe, 0, sizeof pipe);
if (!strncmp(*av, "disable", strlen(*av)))
do_set = 2; /* disable set */
else if (!strncmp(*av, "enable", strlen(*av)))
do_set = 3; /* enable set */
av++; ac--;
if (!strncmp(*av, "set", strlen(*av))) {
if (do_set == 0)
do_set = 1; /* delete set */
ac--; av++;
} else if (do_set != 0)
errx(EX_DATAERR, "missing 'set' keyword");
/* Rule number */
while (ac && isdigit(**av)) {
@ -1671,7 +1720,7 @@ delete(int ac, char *av[])
pipe.fs.fs_nr);
}
} else {
rulenum = i;
rulenum = (i & 0xffff) | (do_set << 16);
i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rulenum,
sizeof rulenum);
if (i) {
@ -2194,6 +2243,15 @@ add(int ac, char *av[])
ac--;
}
/* [set N] -- set number (0..30), optional */
if (ac > 1 && !strncmp(*av, "set", strlen(*av))) {
int set = strtoul(av[1], NULL, 10);
if (set < 0 || set > 30)
errx(EX_DATAERR, "illegal set %s", av[1]);
rule->set = set;
av += 2; ac -= 2;
}
/* [prob D] -- match probability, optional */
if (ac > 1 && !strncmp(*av, "prob", strlen(*av))) {
double d = strtod(av[1], NULL);
@ -2930,7 +2988,7 @@ ipfw_main(int ac, char **av)
do_force = !isatty(STDIN_FILENO);
optind = optreset = 1;
while ((ch = getopt(ac, av, "hs:adefNqtv")) != -1)
while ((ch = getopt(ac, av, "hs:adefNqStv")) != -1)
switch (ch) {
case 'h': /* help */
help();
@ -2957,6 +3015,9 @@ ipfw_main(int ac, char **av)
case 'q':
do_quiet = 1;
break;
case 'S':
show_sets = 1;
break;
case 't':
do_time = 1;
break;
@ -2999,7 +3060,9 @@ ipfw_main(int ac, char **av)
add(ac, av);
else if (do_pipe && !strncmp(*av, "config", strlen(*av)))
config_pipe(ac, av);
else if (!strncmp(*av, "delete", strlen(*av)))
else if (!strncmp(*av, "delete", strlen(*av)) ||
!strncmp(*av, "disable", strlen(*av)) ||
!strncmp(*av, "enable", strlen(*av)))
delete(ac, av);
else if (!strncmp(*av, "flush", strlen(*av)))
flush();

View File

@ -88,7 +88,6 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_TCPACK, /* u32 = desired seq. */
O_ICMPTYPE, /* u32 = icmp bitmap */
O_TCPOPTS, /* arg1 = 2*u8 bitmap */
O_IPOPTS, /* arg1 = 2*u8 bitmap */
O_PROBE_STATE, /* none */
O_KEEP_STATE, /* none */
@ -276,11 +275,12 @@ typedef struct _ipfw_insn_log {
struct ip_fw {
struct ip_fw *next; /* linked list of rules */
struct ip_fw *next_rule; /* ptr to next [skipto] rule */
struct ip_fw *next_rule; /* ptr to next [skipto] rule */
u_int16_t act_ofs; /* offset of action in 32-bit units */
u_int16_t cmd_len; /* # of 32-bit words in cmd */
u_int16_t rulenum; /* rule number */
u_int16_t _pad; /* padding */
u_int8_t set; /* rule set (0..31) */
u_int8_t _pad; /* padding */
/* These fields are present in all rules. */
u_int64_t pcnt; /* Packet counter */

View File

@ -77,6 +77,14 @@
#include <machine/in_cksum.h> /* XXX for in_cksum */
/*
* set_disable contains one bit per set value (0..31).
* If the bit is set, all rules with the corresponding set
* are disabled. Set 31 is reserved for the default rule
* and CANNOT be disabled.
*/
static u_int32_t set_disable;
static int fw_verbose;
static int verbose_limit;
@ -1371,6 +1379,9 @@ ipfw_chk(struct ip_fw_args *args)
int skip_or; /* skip rest of OR block */
again:
if (set_disable & (1 << f->set) )
continue;
skip_or = 0;
for (l = f->cmd_len, cmd = f->cmd ; l > 0 ;
l -= cmdlen, cmd += cmdlen) {
@ -1863,6 +1874,8 @@ ipfw_chk(struct ip_fw_args *args)
next_rule:; /* try next rule */
} /* end of outer for, scan rules */
printf("+++ ipfw: ouch!, skip past end of rules, denying packet\n");
return(IP_FW_PORT_DENY_FLAG);
done:
/* Update statistics */
@ -2042,34 +2055,86 @@ free_chain(struct ip_fw **chain, int kill_default)
}
/**
* Remove all rules with given number.
* Remove all rules with given number, and also do set manipulation.
*
* The argument is an int. The low 16 bit are the
* rule or set number, the upper 16 bits are the
* function, namely:
*
* 0 DEL_SINGLE_RULE
* 1 DELETE_RULESET
* 2 DISABLE_SET
* 3 ENABLE_SET
*/
static int
del_entry(struct ip_fw **chain, u_short rulenum)
del_entry(struct ip_fw **chain, u_int32_t arg)
{
struct ip_fw *prev, *rule;
int s;
u_int16_t rulenum, cmd;
if (rulenum == IPFW_DEFAULT_RULE)
rulenum = arg & 0xffff;
cmd = (arg >> 16) & 0xffff;
if (cmd > 3)
return EINVAL;
if (cmd == 0 && rulenum == IPFW_DEFAULT_RULE)
return EINVAL;
/*
* locate first rule to delete
*/
for (prev = NULL, rule = *chain; rule && rule->rulenum < rulenum;
prev = rule, rule = rule->next)
;
if (rule->rulenum != rulenum)
if (cmd != 0 && rulenum > 30) {
printf("ipfw: del_entry: invalid set number %d\n",
rulenum);
return EINVAL;
}
s = splimp(); /* no access to rules while removing */
flush_rule_ptrs(); /* more efficient to do outside the loop */
/*
* prev remains the same throughout the cycle
*/
while (rule && rule->rulenum == rulenum)
rule = delete_rule(chain, prev, rule);
splx(s);
switch (cmd) {
case 0: /* DEL_SINGLE_RULE */
/*
* locate first rule to delete
*/
for (prev = NULL, rule = *chain;
rule && rule->rulenum < rulenum;
prev = rule, rule = rule->next)
;
if (rule->rulenum != rulenum)
return EINVAL;
s = splimp(); /* no access to rules while removing */
flush_rule_ptrs(); /* more efficient to do outside the loop */
/*
* prev remains the same throughout the cycle
*/
while (rule && rule->rulenum == rulenum)
rule = delete_rule(chain, prev, rule);
splx(s);
break;
case 1: /* DELETE_RULESET */
s = splimp(); /* no access to rules while removing */
flush_rule_ptrs(); /* more efficient to do outside the loop */
for (prev = NULL, rule = *chain; rule ; )
if (rule->set == rulenum)
rule = delete_rule(chain, prev, rule);
else {
prev = rule;
rule = rule->next;
}
splx(s);
break;
case 2: /* DISABLE SET */
s = splimp();
set_disable |= 1 << rulenum;
splx(s);
break;
case 3: /* ENABLE SET */
s = splimp();
set_disable &= ~(1 << rulenum);
splx(s);
break;
}
return 0;
}
@ -2379,6 +2444,11 @@ ipfw_ctl(struct sockopt *sopt)
for (rule = layer3_chain; rule ; rule = rule->next) {
int i = RULESIZE(rule);
bcopy(rule, bp, i);
/*
* abuse 'next_rule' to store the set_disable word
*/
(u_int32_t)(((struct ip_fw *)bp)->next_rule) =
set_disable;
bp = (struct ip_fw *)((char *)bp + i);
}
if (ipfw_dyn_v) {
@ -2446,6 +2516,12 @@ ipfw_ctl(struct sockopt *sopt)
break;
case IP_FW_DEL: /* argument is an int, the rule number */
/*
* IP_FW_DEL is used for deleting single rules,
* set of rules, and manipulating set_disable.
*
* Everything is managed in del_entry();
*/
error = sooptcopyin(sopt, &rulenum, sizeof(int), sizeof(int));
if (error)
break;
@ -2535,6 +2611,7 @@ ipfw_init(void)
default_rule.act_ofs = 0;
default_rule.rulenum = IPFW_DEFAULT_RULE;
default_rule.cmd_len = 1;
default_rule.set = 31;
default_rule.cmd[0].len = 1;
default_rule.cmd[0].opcode =