# ============================================================================ # FILE: ipfw2.patch # PATCHES: /usr/src/sys/netinet/ip_fw.h # /usr/src/sys/netinet/ipfw/ip_fw_dynamic.c # /usr/src/sys/netinet/ipfw/ip_fw_sockopt.c # /usr/src/sbin/ipfw/ipfw2.h # /usr/src/sbin/ipfw/ipfw2.c # /usr/src/sbin/ipfw/ipfw.8 # WRITTEN BY: Aaron D. Gifford - http://www.aarongifford.com/ # 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.12 2010/04/14 15:10:44 adg Exp $ # ============================================================================ --- /usr/src/sys/netinet/ip_fw.h.orig 2010-04-07 12:46:24.000000000 -0600 +++ /usr/src/sys/netinet/ip_fw.h 2010-04-07 12:47:55.000000000 -0600 @@ -129,7 +129,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. */ @@ -528,6 +528,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/ipfw/ip_fw_dynamic.c.orig 2010-04-07 12:50:48.000000000 -0600 +++ /usr/src/sys/netinet/ipfw/ip_fw_dynamic.c 2010-04-07 13:01:54.000000000 -0600 @@ -506,7 +506,7 @@ } } } - q->expire = time_uptime + V_dyn_ack_lifetime; + q->expire = time_uptime + q->lifetime; break; case BOTH_SYN | BOTH_FIN: /* both sides closed */ @@ -530,10 +530,14 @@ break; } } else if (pkt->proto == IPPROTO_UDP) { - q->expire = time_uptime + V_dyn_udp_lifetime; - } else { - /* other protocols */ - q->expire = time_uptime + V_dyn_short_lifetime; + /* + * UDP and OTHER protocols: + * The value of q->lifetime was set at the time this + * dyanmic rule was created. It was set to one of: + * V_dyn_udp_lifetime (for UDP protocol) + * V_dyn_short_lifetime (for OTHER protocols) + */ + q->expire = time_uptime + q->lifetime; } done: if (match_direction) @@ -595,7 +599,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; @@ -627,7 +632,20 @@ } r->id = *id; - r->expire = time_uptime + V_dyn_syn_lifetime; + r->lifetime = lifetime; + if (r->id.proto == IPPROTO_TCP) { + r->lifetime = r->lifetime ? r->lifetime : V_dyn_ack_lifetime; + r->expire = time_uptime + V_dyn_syn_lifetime; + } else { + if (r->lifetime == 0) { + if (r->id.proto == IPPROTO_UDP) { + r->lifetime = V_dyn_udp_lifetime; + } else { + r->lifetime = V_dyn_short_lifetime; + } + } + r->expire = time_uptime + r->lifetime; + } r->rule = rule; r->dyn_type = dyn_type; r->pcnt = r->bcnt = 0; @@ -703,7 +721,7 @@ return q; } } - return add_dyn_rule(pkt, O_LIMIT_PARENT, rule); + return add_dyn_rule(pkt, O_LIMIT_PARENT, rule, 0); } /** @@ -777,7 +795,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 */ @@ -865,7 +884,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: --- /usr/src/sys/netinet/ipfw/ip_fw_sockopt.c.orig 2010-04-07 14:30:04.000000000 -0600 +++ /usr/src/sys/netinet/ipfw/ip_fw_sockopt.c 2010-04-07 14:30:56.000000000 -0600 @@ -560,7 +560,6 @@ } switch (cmd->opcode) { case O_PROBE_STATE: - case O_KEEP_STATE: case O_PROTO: case O_IP_SRC_ME: case O_IP_DST_ME: @@ -612,6 +611,7 @@ } goto check_action; + case O_KEEP_STATE: case O_UID: case O_GID: case O_JAIL: --- /usr/src/sbin/ipfw/ipfw2.h.orig 2010-04-07 13:22:08.000000000 -0600 +++ /usr/src/sbin/ipfw/ipfw2.h 2010-04-07 13:31:11.000000000 -0600 @@ -112,6 +112,7 @@ TOK_IN, TOK_LIMIT, TOK_KEEPSTATE, + TOK_LIFETIME, TOK_LAYER2, TOK_OUT, TOK_DIVERTED, --- /usr/src/sbin/ipfw/ipfw2.c.orig 2010-03-24 17:08:25.000000000 -0600 +++ /usr/src/sbin/ipfw/ipfw2.c 2010-04-07 15:47:38.000000000 -0600 @@ -241,6 +241,7 @@ { "in", TOK_IN }, { "limit", TOK_LIMIT }, { "keep-state", TOK_KEEPSTATE }, + { "lifetime", TOK_LIFETIME }, { "bridged", TOK_LAYER2 }, { "layer2", TOK_LAYER2 }, { "out", TOK_OUT }, @@ -1500,6 +1501,8 @@ case O_KEEP_STATE: printf(" keep-state"); + if (cmd32->d[0]) + printf(" lifetime %u", cmd32->d[0]); break; case O_LIMIT: { @@ -2623,6 +2626,9 @@ struct ip_fw *rule; + /* Temporary pointer to themost recent keep-state command: */ + ipfw_insn_u32 *cmd_keepstate = (ipfw_insn_u32 *)0; + /* * various flags used to record that we entered some fields. */ @@ -3356,7 +3362,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; + av++; break; case TOK_LIMIT: { --- /usr/src/sbin/ipfw/ipfw.8.orig 2010-04-07 13:21:58.000000000 -0600 +++ /usr/src/sbin/ipfw/ipfw.8 2010-04-07 13:37:59.000000000 -0600 @@ -137,7 +137,9 @@ depending on how the kernel is configured. .Pp If the ruleset includes one or more rules with the -.Cm keep-state +.Xo Cm keep-state +.Op Cm lifetime Ar number +.Xc or .Cm limit option, @@ -1378,10 +1380,29 @@ 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 lifetime behavior may be modified for a specific +fule by appending +.Cm lifetime Ar number +immediately after +.Cm keep-state . +Doing so will set the lifetime to the specified number of +seconds for the dynamic rule that gets created when a packet +matches the rule. +.Pp +For TCP rules, setting a 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 protocols, it overrides +.Em net.inet.ip.fw.dyn_short_lifetime . .It Cm layer2 Matches only layer2 packets, i.e., those passed to .Nm