Index: sbin/ipfw/ipfw.8 =================================================================== --- sbin/ipfw/ipfw.8 +++ sbin/ipfw/ipfw.8 @@ -166,9 +166,11 @@ depending on how the kernel is configured. .Pp If the ruleset includes one or more rules with the -.Cm keep-state +.Cm keep-state , +.Cm record-state , +.Cm limit or -.Cm limit +.Cm set-limit option, the firewall will have a .Em stateful @@ -185,6 +187,18 @@ .Cm limit rule, and are typically used to open the firewall on-demand to legitimate traffic only. +Please, note, that +.Cm keep-state +amd +.Cm limit +imply implicit +.Cm check-state +for all packets (not only matched by rule) but +.Cm record-state +and +.Cm set-limit +have not implicit +.Cm check-state . See the .Sx STATEFUL FIREWALL and @@ -582,7 +596,12 @@ packet delivery. .Pp Note: this condition is checked before any other condition, including -ones such as keep-state or check-state which might have side effects. +ones such as +.Cm keep-state +or +.Cm check-state +which might have +side effects. .It Cm log Op Cm logamount Ar number Packets matching a rule with the .Cm log @@ -1583,6 +1602,34 @@ .Xr sysctl 8 variables), and the lifetime is refreshed every time a matching packet is found. +.It Cm record-state +Upon a match, the firewall will create a dynamic rule as if +.Cm keep-state +was specified. +This option doesn't act as +.Cm check-state +in contrast to +.Cm keep-state . +.It Cm set-limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N +Works exactly like +.Cm limit +but rule with this option doesn't have implicit +.Cm check-state +attached to it. +.It Cm skip-immediate-action | skip-action +Rule with this option will not perform normal action +upon a match. This option is intended to be used with +.Cm record-state +or +.Cm keep-state +as dynamic rule, created on match, will work +as intended. +Rule with both +.Cm record-state +and +.Cm skip-immediate-action +creates dynamic rule and continues with next rule, but +when dynamic rule is matched, action is executed as usual. .It Cm layer2 Matches only layer2 packets, i.e., those passed to .Nm @@ -2164,16 +2211,18 @@ match a given pattern are detected. Support for stateful operation comes through the -.Cm check-state , keep-state +.Cm check-state , keep-state , record-state , limit and -.Cm limit +.Cm set-limit options of .Nm rules . .Pp Dynamic rules are created when a packet matches a -.Cm keep-state +.Cm keep-state , +.Cm record-state , +.Cm limit or -.Cm limit +.Cm set-limit rule, causing the creation of a .Em dynamic rule which will match all and only packets with Index: sbin/ipfw/ipfw2.h =================================================================== --- sbin/ipfw/ipfw2.h +++ sbin/ipfw/ipfw2.h @@ -115,7 +115,9 @@ TOK_JAIL, TOK_IN, TOK_LIMIT, + TOK_SETLIMIT, TOK_KEEPSTATE, + TOK_RECORDSTATE, TOK_LAYER2, TOK_OUT, TOK_DIVERTED, @@ -227,6 +229,7 @@ TOK_LOCK, TOK_UNLOCK, TOK_VLIST, + TOK_SKIPACTION, }; /* Index: sbin/ipfw/ipfw2.c =================================================================== --- sbin/ipfw/ipfw2.c +++ sbin/ipfw/ipfw2.c @@ -291,7 +291,9 @@ { "jail", TOK_JAIL }, { "in", TOK_IN }, { "limit", TOK_LIMIT }, + { "set-limit", TOK_SETLIMIT }, { "keep-state", TOK_KEEPSTATE }, + { "record-state", TOK_RECORDSTATE }, { "bridged", TOK_LAYER2 }, { "layer2", TOK_LAYER2 }, { "out", TOK_OUT }, @@ -354,6 +356,8 @@ { "src-ip6", TOK_SRCIP6}, { "lookup", TOK_LOOKUP}, { "flow", TOK_FLOW}, + { "skip-action", TOK_SKIPACTION }, + { "skip-immediate-action", TOK_SKIPACTION }, { "//", TOK_COMMENT }, { "not", TOK_NOT }, /* pseudo option */ @@ -1384,6 +1388,7 @@ ipfw_insn_altq *altqptr = NULL; /* set if we find an O_ALTQ */ int or_block = 0; /* we are in an or block */ uint32_t uval; + int have_probe_state = 0; if ((fo->set_mask & (1 << rule->set)) == 0) { /* disabled mask */ @@ -1653,6 +1658,7 @@ break; /* done already */ case O_PROBE_STATE: + have_probe_state = 1; break; /* no need to print anything here */ case O_IP_SRC: @@ -1990,7 +1996,10 @@ break; case O_KEEP_STATE: - bprintf(bp, " keep-state"); + if (have_probe_state) + bprintf(bp, " keep-state"); + else + bprintf(bp, " record-state"); break; case O_LIMIT: { @@ -1999,7 +2008,11 @@ uint8_t x = c->limit_mask; char const *comma = " "; - bprintf(bp, " limit"); + if (have_probe_state) + bprintf(bp, " limit"); + else + bprintf(bp, " set-limit"); + for (; p->x != 0 ; p++) if ((x & p->x) == p->x) { x &= ~p->x; @@ -2035,6 +2048,10 @@ 0, O_TAGGED); break; + case O_SKIP_ACTION: + bprintf(bp, " skip-immediate-action"); + break; + default: bprintf(bp, " [opcode %d len %d]", cmd->opcode, cmd->len); @@ -3485,8 +3502,10 @@ /* * various flags used to record that we entered some fields. */ - ipfw_insn *have_state = NULL; /* check-state or keep-state */ + ipfw_insn *have_state = NULL; /* any state-related option */ + int have_rstate = 0; ipfw_insn *have_log = NULL, *have_altq = NULL, *have_tag = NULL; + ipfw_insn *have_skipcmd = NULL; size_t len; int i; @@ -4335,27 +4354,31 @@ break; case TOK_KEEPSTATE: + case TOK_RECORDSTATE: if (open_par) - errx(EX_USAGE, "keep-state cannot be part " + errx(EX_USAGE, "keep-state or record-state cannot be part " "of an or block"); if (have_state) - errx(EX_USAGE, "only one of keep-state " - "and limit is allowed"); + errx(EX_USAGE, "only one of keep-state, record-state, " + " limit and set-limit is allowed"); have_state = cmd; + have_rstate = i == TOK_RECORDSTATE; fill_cmd(cmd, O_KEEP_STATE, 0, 0); break; - case TOK_LIMIT: { + case TOK_LIMIT: + case TOK_SETLIMIT: { ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; int val; if (open_par) errx(EX_USAGE, - "limit cannot be part of an or block"); + "limit or set-limit cannot be part of an or block"); if (have_state) - errx(EX_USAGE, "only one of keep-state and " - "limit is allowed"); + errx(EX_USAGE, "only one of keep-state, record-state, " + " limit and set-limit is allowed"); have_state = cmd; + have_rstate = i == TOK_SETLIMIT; cmd->len = F_INSN_SIZE(ipfw_insn_limit); CHECK_CMDLEN; @@ -4539,6 +4562,7 @@ av++; } break; + case TOK_FLOW: NEED1("missing table name"); if (strncmp(*av, "table(", 6) != 0) @@ -4548,6 +4572,14 @@ av++; break; + case TOK_SKIPACTION: + if (have_skipcmd) + errx(EX_USAGE, "only one skip-action " + "is allowed"); + have_skipcmd = cmd; + fill_cmd(cmd, O_SKIP_ACTION, 0, 0); + break; + default: errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s); } @@ -4558,6 +4590,11 @@ } done: + + if (!have_state && have_skipcmd) + warnx("Rule contains \"skip-immediate-action\" " + "and doesn't contain any state-related options."); + /* * Now copy stuff into the rule. * If we have a keep-state option, the first instruction @@ -4580,12 +4617,15 @@ /* * generate O_PROBE_STATE if necessary */ - if (have_state && have_state->opcode != O_CHECK_STATE) { + if (have_state && have_state->opcode != O_CHECK_STATE && !have_rstate) { fill_cmd(dst, O_PROBE_STATE, 0, 0); dst = next_cmd(dst, &rblen); } - /* copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG */ + /* + * copy all commands but O_LOG, O_KEEP_STATE, O_LIMIT, O_ALTQ, O_TAG, + * O_SKIP_ACTION + */ for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) { i = F_LEN(src); CHECK_RBUFLEN(i); @@ -4596,6 +4636,7 @@ case O_LIMIT: case O_ALTQ: case O_TAG: + case O_SKIP_ACTION: break; default: bcopy(src, dst, i * sizeof(uint32_t)); @@ -4612,7 +4653,18 @@ bcopy(have_state, dst, i * sizeof(uint32_t)); dst += i; } + /* + * put back the have_skipcmd command as very last opcode + */ + if (have_skipcmd) { + i = F_LEN(have_skipcmd); + CHECK_RBUFLEN(i); + bcopy(have_skipcmd, dst, i * sizeof(uint32_t)); + dst += i; + } + + /* * start action section */ rule->act_ofs = dst - rule->cmd; @@ -4963,7 +5015,3 @@ free(olh); } - - - - Index: sys/netinet/ip_fw.h =================================================================== --- sys/netinet/ip_fw.h +++ sys/netinet/ip_fw.h @@ -252,6 +252,8 @@ O_DSCP, /* 2 u32 = DSCP mask */ O_SETDSCP, /* arg1=DSCP value */ O_IP_FLOW_LOOKUP, /* arg1=table number, u32=value */ + + O_SKIP_ACTION, /* none */ O_LAST_OPCODE /* not an opcode! */ }; Index: sys/netpfil/ipfw/ip_fw2.c =================================================================== --- sys/netpfil/ipfw/ip_fw2.c +++ sys/netpfil/ipfw/ip_fw2.c @@ -2109,7 +2109,9 @@ * * O_LIMIT and O_KEEP_STATE: these opcodes are * not real 'actions', and are stored right - * before the 'action' part of the rule. + * before the 'action' part of the rule (one + * exception is O_SKIP_ACTION which could be + * between these opcodes and 'action' one). * These opcodes try to install an entry in the * state tables; if successful, we continue with * the next opcode (match=1; break;), otherwise @@ -2126,11 +2128,22 @@ * further instances of these opcodes become NOPs. * The jump to the next rule is done by setting * l=0, cmdlen=0. + * + * O_SKIP_ACTION: this opcode is not real 'action' + * too, and is stored right before the 'action' + * part of the rule, right after O_KEEP_STATE + * opcode. It causes match failure so real + * 'action' could be executed only if rule + * is checked via dynamic rule from state + * table, as in such case execution starts + * from true 'action' opcode directly. + * */ case O_LIMIT: case O_KEEP_STATE: - if (ipfw_install_state(chain, f, - (ipfw_insn_limit *)cmd, args, tablearg)) { + if (ipfw_install_or_update_state(chain, f, + (ipfw_insn_limit *)cmd, args, tablearg, + proto == IPPROTO_TCP ? TCP(ulp) : NULL)) { /* error or limit violation */ retval = IP_FW_DENY; l = 0; /* exit inner loop */ @@ -2187,6 +2200,11 @@ match = 1; break; + case O_SKIP_ACTION: + match = 0; /* skip to the next rule */ + l = 0; /* exit inner loop */ + break; + case O_ACCEPT: retval = 0; /* accept */ l = 0; /* exit inner loop */ @@ -2537,7 +2555,7 @@ done = 1; /* exit outer loop */ break; } - + default: panic("-- unknown opcode %d\n", cmd->opcode); } /* end of switch() on opcodes */ Index: sys/netpfil/ipfw/ip_fw_dynamic.c =================================================================== --- sys/netpfil/ipfw/ip_fw_dynamic.c +++ sys/netpfil/ipfw/ip_fw_dynamic.c @@ -669,13 +669,15 @@ /** * Install dynamic state for rule type cmd->o.opcode + * If rule exists, update it state. * * Returns 1 (failure) if state is not installed because of errors or because * session limitations are enforced. */ int -ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, - ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg) +ipfw_install_or_update_state(struct ip_fw_chain *chain, struct ip_fw *rule, + ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg, + struct tcphdr *tcp) { ipfw_dyn_rule *q; int i; @@ -686,9 +688,9 @@ IPFW_BUCK_LOCK(i); - q = lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL); + q = lookup_dyn_rule_locked(&args->f_id, i, NULL, tcp); - if (q != NULL) { /* should never occur */ + if (q != NULL) { /* could occur in case of "record-state" */ DEB( if (last_log != time_uptime) { last_log = time_uptime; Index: sys/netpfil/ipfw/ip_fw_private.h =================================================================== --- sys/netpfil/ipfw/ip_fw_private.h +++ sys/netpfil/ipfw/ip_fw_private.h @@ -183,8 +183,9 @@ struct tcphdr; struct mbuf *ipfw_send_pkt(struct mbuf *, struct ipfw_flow_id *, u_int32_t, u_int32_t, int); -int ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, - ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg); +int ipfw_install_or_update_state(struct ip_fw_chain *chain, struct ip_fw *rule, + ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg, + struct tcphdr *tcp); ipfw_dyn_rule *ipfw_lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, struct tcphdr *tcp); void ipfw_remove_dyn_children(struct ip_fw *rule); Index: sys/netpfil/ipfw/ip_fw_sockopt.c =================================================================== --- sys/netpfil/ipfw/ip_fw_sockopt.c +++ sys/netpfil/ipfw/ip_fw_sockopt.c @@ -1460,6 +1460,7 @@ #endif case O_IP4: case O_TAG: + case O_SKIP_ACTION: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; break;