Support and document new stateful ipfw features.

Approved-by: jordan
This commit is contained in:
luigi 2000-02-10 14:25:26 +00:00
parent 4c77286bf0
commit f6954f1a86
2 changed files with 290 additions and 15 deletions

View File

@ -30,6 +30,7 @@ flush
{zero|resetlog|delete}
.Op Ar number ...
.Nm ipfw
.Op Fl s Op field
.Op Fl aftN
{list|show}
.Op Ar number ...
@ -83,7 +84,32 @@ or
.Ar allow
depending on how the kernel is configured.
.Pp
All rules have a few associated counters: a packet count and
If the ruleset includes one or more rules with the
.Ar keep-state
option, then
.Nm ipfw
assumes a
.Nm stateful
behaviour, i.e. upon a match
will create dynamic rules matching the exact parameters
(addresses and ports) of the matching packet.
.Pp
These dynamic rules, which have a limited lifetime,
are checked at the first occurrence of a
.Ar check-state
or
.Ar keep-state
rule, and are typically
used to open the firewall on-demand to legitimate traffic
only. See the
.Xr options
and
.Xr EXAMPLES
sections for more information on the stateful behaviour of
.Nm ipfw
.Pp
All rules (including dynamic ones)
have a few associated counters: a packet count and
a byte count, a log count, and a timestamp indicating the time
of the last match. Counters can be visualized or reset with
.Nm
@ -140,6 +166,9 @@ not processed. Access to the console is required to recover.
While listing, show last match timestamp.
.It Fl N
Try to resolve addresses and service names in output.
.It Fl s Op field
While listing pipes, sort according one of the four
counters (total and current packets or bytes).
.El
.Pp
To ease configuration, rules can be put into a file which is processed
@ -281,6 +310,15 @@ The search terminates.
.It Ar count
Update counters for all packets that match rule.
The search continues with the next rule.
.It Ar check-state
Checks the packet against the dynamic ruleset. If a match is
found then the search terminates, otherwise we move to the
next rule.
If no
.Ar check-state
rule is found, the dynamic ruleset is checked at the first
.Ar keep-state
rule.
.It Ar divert port
Divert packets that match this rule to the
.Xr divert 4
@ -508,6 +546,30 @@ the local host have no transmit interface.
.Pp
.Ar options :
.Bl -hang -offset flag -width 1234567890123456
.It keep-state Op method
Upon a match, the firewall will create a dynamic rule,
whose default behaviour is to
matching bidirectional traffic between source and destination
IP/port using the same protocol. The rule has a limited lifetime
(controlled by a set of
.Nm sysctl
variables), and the lifetime is refreshed every time a matching packet
is found.
.Pp
The actual behaviour can be modified by specifying a different
.Op method ,
although at the moment only the default one is specified.
.It bridged
Matches only bridged packets. This can be useful for multicast
or broadcast traffic, which would otherwise pass through the
firewall twice: once during bridging, and a second time
when the packet is delivered to the local stack.
.Pp
Apart from a small performance penalty, this would be a problem
when using
.Ar pipes
because the same packet would be accounted for twice
in terms of bandwidth, queue occupation, and also counters.
.It frag
Match if the packet is a fragment and this is not the first fragment
of the datagram.
@ -724,6 +786,51 @@ to that port; see
.Xr divert 4 .
If no socket is bound to the destination port, or if the kernel
wasn't compiled with divert socket support, the packets are dropped.
.Pp
.Sh SYSCTL VARIABLES
A set of
.Nm sysctl
variables controls the behaviour of the firewall. These are shown
below together with their default value and meaning
.Bl -tag -offset flag -width 1234567890
.It "net.inet.ip.fw.debug: 1"
Controls debugging messages produced by ipfw.
.It "net.inet.ip.fw.one_pass: 1"
When set, permits only one pass through the firewall.
Otherwise, after a
pipe or divert
action, the packet is reinjected in the firewall starting from
the next rule.
.It net.inet.ip.fw.verbose: 1
Enables verbose messages.
.It net.inet.ip.fw.enable: 1
Enables the firewall. Setting this variable to 0 lets you run your
machine without firewall even if compiled in.
.It net.inet.ip.fw.verbose_limit: 0
Limits the number of messages produced by a verbose firewall.
.It net.inet.ip.fw.dyn_buckets: 256
.It net.inet.ip.fw.curr_dyn_buckets: 256
The configured and current size of the hash table used to hold
dynamic rules. This must be a power of 2. The table can only
be resized when empty, so in order to resize it on the fly you
will probably have to
.Ar flush
and reload the ruleset.
.It net.inet.ip.fw.dyn_count: 3
(readonly) current number of dynamic rules.
.It net.inet.ip.fw.dyn_max: 1000
Maximum number of dynamic rules. When you hit this limit,
no more dynamic rules can be installed until old ones expire.
.It net.inet.ip.fw.dyn_ack_lifetime: 300
.It net.inet.ip.fw.dyn_syn_lifetime: 20
.It net.inet.ip.fw.dyn_fin_lifetime: 20
.It net.inet.ip.fw.dyn_rst_lifetime: 5
.It net.inet.ip.fw.dyn_short_lifetime: 30
These variable control the lifetime, in seconds, of dynamic rules.
Upon the initial SYN exchange the lifetime is kept short,
then increased after both SYN have been seen, then decreased
again during the final FIN exchange or when a RST
.El
.Sh EXAMPLES
This command adds an entry which denies all tcp packets from
.Em cracker.evil.org
@ -738,6 +845,54 @@ my host:
.Pp
.Dl ipfw add deny all from 123.45.67.0/24 to my.host.org
.Pp
A first and efficient way to limit access (not using dynamic rules)
is the use of the following rules
.Pp
.Dl "ipfw add allow tcp from any to any established"
.Dl "ipfw add allow tcp from net1 portlist1 to net2 portlist2 setup"
.Dl "ipfw add allow tcp from net3 portlist3 to net3 portlist3 setup"
.Dl "..."
.Dl "ipfw add deny tcp from any to any"
.Pp
The first rule will be a quick match for normal TCP packets, but
it will not match the initial SYN packet, which will be
matched by the
.Ar setup
rules only for selected source/destination pairs.
All other SYN packets will be rejected by the final
.Ar deny
rule.
.Pp
In order to protect a site from flood attacks involving fake
TCP packets, it is safer to use dynamic rules:
.Pp
.Dl "ipfw add check-state"
.Dl "ipfw add deny tcp from any to any established"
.Dl "ipfw add allow tcp from my-net to any setup keep-state"
.Pp
This will let the firewall install dynamic rules only for
those connection which start with a regular SYN
packet coming from the inside of our network. Dynamic rules
are checked when encountering the first
.Ar check-state
or
.Ar keep-state
rule. A
.Ar check-state
rule should be usually placed near the beginning of the ruleset
to minimize the amount of work scanning the ruleset. Your mileage
may vary.
.Pp
BEWARE:
stateful rules can be subject to denial-of-service attacks
by a SYN-flood which opens a huge number of dynamic rules.
The effects of such attacks can be partially limited by acting on
a set of
.Nm sysctl
variables which control the operation of the firewall.
.Pp
There is a number of sysctl variables which controls the
.Pp
Here is a good usage of the
.Ar list
command to see accounting records
@ -884,4 +1039,6 @@ first appeared in
.Fx 2.0 .
.Nm dummynet
was introduced in
.Fx 2.2.8
.Fx 2.2.8 .
Stateful extensions were introduced in
.Fx 4.0-RELEASE

View File

@ -68,6 +68,7 @@ int do_time=0; /* Show time stamps */
int do_quiet=0; /* Be quiet in add and flush */
int do_force=0; /* Don't ask for confirmation */
int do_pipe=0; /* this cmd refers to a pipe */
int do_sort=0; /* field to sort results (0=no) */
struct icmpcode {
int code;
@ -211,6 +212,10 @@ show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth)
else
printf(" ");
}
if (chain->fw_flg == IP_FW_F_CHECK_S) {
printf("check-state\n");
goto done ;
}
switch (chain->fw_flg & IP_FW_F_COMMAND)
{
@ -371,7 +376,15 @@ show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth)
printf(" gid %u", chain->fw_gid);
}
/* Direction */
if (chain->fw_flg & IP_FW_F_KEEP_S) {
if (chain->next_rule_ptr)
printf(" keep-state %d", (int)chain->next_rule_ptr);
else
printf(" keep-state");
}
/* Direction */
if (chain->fw_flg & IP_FW_BRIDGED)
printf(" bridged");
if ((chain->fw_flg & IP_FW_F_IN) && !(chain->fw_flg & IP_FW_F_OUT))
printf(" in");
if (!(chain->fw_flg & IP_FW_F_IN) && (chain->fw_flg & IP_FW_F_OUT))
@ -449,10 +462,41 @@ show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth)
}
}
printf("\n");
done:
if (do_resolv)
endservent();
}
int
sort_q(const void *pa, const void *pb)
{
int rev = (do_sort < 0) ;
int field = rev ? -do_sort : do_sort ;
long long res=0 ;
const struct dn_flow_queue *a = pa ;
const struct dn_flow_queue *b = pb ;
switch (field) {
case 1: /* pkts */
res = a->len - b->len ;
break ;
case 2 : /* bytes */
res = a->len_bytes - b->len_bytes ;
break ;
case 3 : /* tot pkts */
res = a->tot_pkts - b->tot_pkts ;
break ;
case 4 : /* tot bytes */
res = a->tot_bytes - b->tot_bytes ;
break ;
}
if (res < 0) res = -1 ;
if (res > 0) res = 1 ;
return (int)(rev ? res : -res) ;
}
static void
list(ac, av)
int ac;
@ -537,33 +581,41 @@ list(ac, av)
p->flow_mask.proto,
p->flow_mask.src_ip, p->flow_mask.src_port,
p->flow_mask.dst_ip, p->flow_mask.src_port);
printf("BKT Prot ___Source IP/port____ "
"____Dest. IP/port____ Tot_pkt/bytes Pkt/Byte Drop\n");
if (do_sort != 0)
heapsort(q, p->rq_elements, sizeof( *q), sort_q);
for (l = 0 ; l < p->rq_elements ; l++) {
struct in_addr ina ;
struct protoent *pe ;
ina.s_addr = htonl(q[l].id.src_ip) ;
printf(" (%d) ", q[l].hash_slot);
printf("%3d ", q[l].hash_slot);
pe = getprotobynumber(q[l].id.proto);
if (pe)
printf(" %s", pe->p_name);
printf("%-4s ", pe->p_name);
else
printf(" %u", q[l].id.proto);
printf(" %s/%d -> ",
printf("%4u ", q[l].id.proto);
printf("%15s/%-5d ",
inet_ntoa(ina), q[l].id.src_port);
ina.s_addr = htonl(q[l].id.dst_ip) ;
printf("%s/%d\n",
printf("%15s/%-5d ",
inet_ntoa(ina), q[l].id.dst_port);
printf("\t%u pkts %u bytes, tot %qu pkts %qu bytes %u drops\n",
q[l].len, q[l].len_bytes,
q[l].tot_pkts, q[l].tot_bytes, q[l].drops);
printf("%4qu %8qu %2u %4u %3u\n",
q[l].tot_pkts, q[l].tot_bytes,
q[l].len, q[l].len_bytes, q[l].drops);
}
}
free(data);
return;
}
/* if showing stats, figure out column widths ahead of time */
rules = (struct ip_fw *) data;
/* determine num more accurately */
num = 0;
while (rules[num].fw_number < 65535)
num++ ;
num++ ; /* counting starts from 0 ... */
/* if showing stats, figure out column widths ahead of time */
if (do_acct) {
for (n = 0; n < num; n++) {
struct ip_fw *const r = &rules[n];
@ -624,6 +676,45 @@ list(ac, av)
if (exitval != EX_OK)
exit(exitval);
}
/*
* show dynamic rules
*/
if (num * sizeof (rules[0]) != nbytes ) {
struct ipfw_dyn_rule *d =
(struct ipfw_dyn_rule *)&rules[num] ;
struct in_addr a ;
struct protoent *pe;
printf("## Dynamic rules:\n");
for (;; d++) {
printf("%05d %qu %qu (T %d, # %d) ty %d",
(int)(d->chain),
d->pcnt, d->bcnt,
d->expire,
d->bucket,
d->type);
pe = getprotobynumber(d->id.proto);
if (pe)
printf(" %s,", pe->p_name);
else
printf(" %u,", d->id.proto);
a.s_addr = htonl(d->id.src_ip);
printf(" %s", inet_ntoa(a));
printf(" %d", d->id.src_port);
switch (d->type) {
default: /* bidir, no mask */
printf(" <->");
break ;
}
a.s_addr = htonl(d->id.dst_ip);
printf(" %s", inet_ntoa(a));
printf(" %d", d->id.dst_port);
printf("\n");
if (d->next == NULL)
break ;
}
}
free(data);
}
@ -1303,6 +1394,9 @@ add(ac,av)
} else if (!strncmp(*av,"unreach",strlen(*av))) {
rule.fw_flg |= IP_FW_F_REJECT; av++; ac--;
fill_reject_code(&rule.fw_reject_code, *av); av++; ac--;
} else if (!strncmp(*av,"check-state",strlen(*av))) {
rule.fw_flg |= IP_FW_F_CHECK_S ; av++; ac--;
goto done ;
} else {
show_usage("invalid action ``%s''", *av);
}
@ -1450,6 +1544,21 @@ add(ac,av)
rule.fw_flg |= IP_FW_F_IN;
av++; ac--; continue;
}
if (!strncmp(*av,"keep-state",strlen(*av))) {
u_long type ;
rule.fw_flg |= IP_FW_F_KEEP_S;
av++; ac--;
if (ac > 0 && (type = atoi(*av)) != 0) {
(int)rule.next_rule_ptr = type ;
av++; ac--;
}
continue;
}
if (!strncmp(*av,"bridged",strlen(*av))) {
rule.fw_flg |= IP_FW_BRIDGED;
av++; ac--; continue;
}
if (!strncmp(*av,"out",strlen(*av))) {
rule.fw_flg |= IP_FW_F_OUT;
av++; ac--; continue;
@ -1579,7 +1688,7 @@ add(ac,av)
}
rule.fw_loghighest = rule.fw_logamount;
}
done:
if (!do_quiet)
show_ipfw(&rule, 10, 10);
i = setsockopt(s, IPPROTO_IP, IP_FW_ADD, &rule, sizeof rule);
@ -1682,8 +1791,11 @@ ipfw_main(ac,av)
do_force = !isatty(STDIN_FILENO);
optind = optreset = 1;
while ((ch = getopt(ac, av, "afqtN")) != -1)
while ((ch = getopt(ac, av, "s:afqtN")) != -1)
switch(ch) {
case 's': /* sort */
do_sort= atoi(optarg);
break;
case 'a':
do_acct=1;
break;
@ -1795,6 +1907,12 @@ main(ac, av)
setbuf(stdout,0);
/*
* this is a nasty check on the last argument!!!
* If there happens to be a filename matching a keyword in the current
* directory, things will fail miserably.
*/
if (ac > 1 && access(av[ac - 1], R_OK) == 0) {
qflag = pflag = i = 0;
lineno = 0;