Support and document new stateful ipfw features.
Approved-by: jordan
This commit is contained in:
parent
6bc748b057
commit
d69f84c0b4
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=57115
161
sbin/ipfw/ipfw.8
161
sbin/ipfw/ipfw.8
@ -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
|
||||
|
142
sbin/ipfw/ipfw.c
142
sbin/ipfw/ipfw.c
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user