# # FILE: ipfw2.patch # PATCHES: /usr/src/sys/netinet/ip_fw.h # /usr/src/sys/netinet/ip_fw2.c # /usr/src/sbin/ipfw/ipfw2.c # /usr/src/sbin/ipfw/ipfw.8 # WRITTEN BY: Aaron D. Gifford - http://www.adg.us/ # PURPOSE: Patch the FreeBSD kernel's ipfw2 system to support changing # dynamic rule expiration lifetime on a per-rule basis, which # allows for much tighter traffic control, including controlling # the timing of firewall-generated keep-alive packets if that # feature is enabled. This set of patches patches the kernel, # the ipfw command, and the man page for the IPFW(8) utility. # # $Id: ipfw2.patch,v 1.6 2005/11/02 23:02:47 adg Exp $ # --- /usr/src/sys/netinet/ip_fw.h.orig Sat Aug 20 02:36:57 2005 +++ /usr/src/sys/netinet/ip_fw.h Wed Nov 2 15:31:04 2005 @@ -101,7 +101,7 @@ O_VERSRCREACH, /* none */ O_PROBE_STATE, /* none */ - O_KEEP_STATE, /* none */ + O_KEEP_STATE, /* u32 = optional lifetime */ O_LIMIT, /* ipfw_insn_limit */ O_LIMIT_PARENT, /* dyn_type, not an opcode. */ @@ -426,6 +426,7 @@ u_int64_t bcnt; /* byte match counter */ struct ipfw_flow_id id; /* (masked) flow id */ u_int32_t expire; /* expire time */ + u_int32_t lifetime; /* per-rule lifetime */ u_int32_t bucket; /* which bucket in hash table */ u_int32_t state; /* state of this rule (typically a * combination of TCP flags) --- /usr/src/sys/netinet/ip_fw2.c.orig Sat Sep 17 07:43:36 2005 +++ /usr/src/sys/netinet/ip_fw2.c Wed Nov 2 15:31:04 2005 @@ -1250,7 +1250,7 @@ } } } - q->expire = time_second + dyn_ack_lifetime; + q->expire = time_second + q->lifetime; break; case BOTH_SYN | BOTH_FIN: /* both sides closed */ @@ -1273,11 +1273,16 @@ q->expire = time_second + dyn_rst_lifetime; break; } - } else if (pkt->proto == IPPROTO_UDP) { - q->expire = time_second + dyn_udp_lifetime; } else { - /* other protocols */ - q->expire = time_second + dyn_short_lifetime; + /* + * UDP and other protocols: + * NOTE: The value of q->lifetime was set at the time this + * dynamic rule was created. It was either explicitly set + * by the ruleset creator to a specific value, or was pre- + * set to either dyn_udp_lifetime for UDP, or to + * dyn_short_lifetime for non-UDP protocols. + */ + q->expire = time_second + q->lifetime; } done: if (match_direction) @@ -1339,7 +1344,8 @@ * - "parent" rules for the above (O_LIMIT_PARENT). */ static ipfw_dyn_rule * -add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule) +add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule, + u_int32_t lifetime) { ipfw_dyn_rule *r; int i; @@ -1371,7 +1377,20 @@ } r->id = *id; - r->expire = time_second + dyn_syn_lifetime; + r->lifetime = lifetime; + if (r->id.proto == IPPROTO_TCP) { + r->lifetime = r->lifetime ? r->lifetime : dyn_ack_lifetime; + r->expire = time_second + dyn_syn_lifetime; + } else { + if (r->lifetime == 0) { + if (r->id.proto == IPPROTO_UDP) { + r->lifetime = dyn_udp_lifetime; + } else { + r->lifetime = dyn_short_lifetime; + } + } + r->expire = time_second + r->lifetime; + } r->rule = rule; r->dyn_type = dyn_type; r->pcnt = r->bcnt = 0; @@ -1426,7 +1445,7 @@ return q; } } - return add_dyn_rule(pkt, O_LIMIT_PARENT, rule); + return add_dyn_rule(pkt, O_LIMIT_PARENT, rule, 0); } /** @@ -1478,7 +1497,8 @@ switch (cmd->o.opcode) { case O_KEEP_STATE: /* bidir rule */ - add_dyn_rule(&args->f_id, O_KEEP_STATE, rule); + add_dyn_rule(&args->f_id, O_KEEP_STATE, rule, + ((ipfw_insn_u32 *)cmd)->d[0]); break; case O_LIMIT: /* limit number of sessions */ @@ -1530,7 +1550,7 @@ return 1; } } - add_dyn_rule(&args->f_id, O_LIMIT, (struct ip_fw *)parent); + add_dyn_rule(&args->f_id, O_LIMIT, (struct ip_fw *)parent, 0); } break; default: @@ -3552,7 +3572,6 @@ DEB(printf("ipfw: opcode %d\n", cmd->opcode);) switch (cmd->opcode) { case O_PROBE_STATE: - case O_KEEP_STATE: case O_PROTO: case O_IP_SRC_ME: case O_IP_DST_ME: @@ -3583,6 +3602,7 @@ goto bad_size; break; + case O_KEEP_STATE: case O_UID: case O_GID: case O_JAIL: --- /usr/src/sbin/ipfw/ipfw2.c.orig Sat Aug 20 02:36:57 2005 +++ /usr/src/sbin/ipfw/ipfw2.c Wed Nov 2 15:31:04 2005 @@ -219,6 +219,7 @@ TOK_IN, TOK_LIMIT, TOK_KEEPSTATE, + TOK_LIFETIME, TOK_LAYER2, TOK_OUT, TOK_DIVERTED, @@ -350,6 +351,7 @@ { "in", TOK_IN }, { "limit", TOK_LIMIT }, { "keep-state", TOK_KEEPSTATE }, + { "lifetime", TOK_LIFETIME }, { "bridged", TOK_LAYER2 }, { "layer2", TOK_LAYER2 }, { "out", TOK_OUT }, @@ -1876,6 +1878,8 @@ case O_KEEP_STATE: printf(" keep-state"); + if (cmd32->d[0]) + printf(" lifetime %u", cmd32->d[0]); break; case O_LIMIT: @@ -3728,6 +3732,9 @@ struct ip_fw *rule; + /* Temporary pointer to the most recent keep-state command: */ + ipfw_insn_u32 *cmd_keepstate = (ipfw_insn_u32 *)0; + /* * various flags used to record that we entered some fields. */ @@ -4412,7 +4419,20 @@ errx(EX_USAGE, "only one of keep-state " "and limit is allowed"); have_state = cmd; - fill_cmd(cmd, O_KEEP_STATE, 0, 0); + cmd->opcode = O_KEEP_STATE; + cmd->len = F_INSN_SIZE(ipfw_insn_u32); + cmd32->d[0] = 0; + cmd_keepstate = cmd32; + break; + + case TOK_LIFETIME: + if (cmd_keepstate == (ipfw_insn_u32 *)0) + errx(EX_USAGE, "lifetime must immediately " + "follow keep-state"); + NEED1("lifetime requires # of seconds"); + cmd_keepstate->d[0] = strtoul(*av, NULL, 0); + cmd_keepstate = (ipfw_insn_u32 *)0; + ac--; av++; break; case TOK_LIMIT: --- /usr/src/sbin/ipfw/ipfw.8.orig Wed Oct 26 15:58:42 2005 +++ /usr/src/sbin/ipfw/ipfw.8 Wed Nov 2 15:31:04 2005 @@ -1219,14 +1219,35 @@ .It Cm ipversion Ar ver Matches IP packets whose IP version field is .Ar ver . -.It Cm keep-state +.It Xo Cm keep-state +.Op Cm lifetime Ar number +.Xc Upon a match, the firewall will create a dynamic rule, whose default behaviour is to match bidirectional traffic between source and destination IP/port using the same protocol. -The rule has a limited lifetime (controlled by a set of +The rule has a limited lifetime controlled by a set of .Xr sysctl 8 -variables), and the lifetime is refreshed every time a matching +variables, and the lifetime is refreshed every time a matching packet is found. +.Pp +The default limited rule lifetime behavior may be modified +for a specific rule by appending +.Cm lifetime Ar number +immediately after +.Cm keep-state . +Doing so will explicitly set the dynamic rule lifetime to the +specified number of seconds, overriding the default lifetime +behavior for the specified rule. +.Pp +For TCP rules, explicitly setting a rule lifetime overrides the +default setting stored in the +.Xr sysctl 8 +variable +.Em net.inet.ip.fw.dyn_ack_lifetime . +For UDP rules, it overrides +.Em net.inet.ip.fw.dyn_udp_lifetime . +For all other rules, it overrides +.Em net.inet.ip.fw.dyn_short_lifetime . .It Cm layer2 Matches only layer2 packets, i.e., those passed to .Nm