From d69f84c0b4fb2437fed0e70be2a0b2c681927f24 Mon Sep 17 00:00:00 2001 From: Luigi Rizzo Date: Thu, 10 Feb 2000 14:25:26 +0000 Subject: [PATCH] Support and document new stateful ipfw features. Approved-by: jordan --- sbin/ipfw/ipfw.8 | 161 ++++++++++++++++++++++++++++++++++++++++++++++- sbin/ipfw/ipfw.c | 144 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 290 insertions(+), 15 deletions(-) diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 7057f3958dbe..3994cab3b58d 100644 --- a/sbin/ipfw/ipfw.8 +++ b/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 diff --git a/sbin/ipfw/ipfw.c b/sbin/ipfw/ipfw.c index 04d02ffb03f1..7cea1a2e9498 100644 --- a/sbin/ipfw/ipfw.c +++ b/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); } - /* 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;