Index: sbin/ipfw/ipfw.8 =================================================================== --- sbin/ipfw/ipfw.8 +++ sbin/ipfw/ipfw.8 @@ -179,9 +179,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 @@ -198,6 +200,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 these matched by the rule) but +.Cm record-state +and +.Cm set-limit +have no implicit +.Cm check-state . See the .Sx STATEFUL FIREWALL and @@ -595,7 +609,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 @@ -1379,6 +1398,21 @@ .It Cm bridged Alias for .Cm layer2 . +.It Cm defer-immediate-action | defer-action +A 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 the dynamic rule, created but ignored on match, will work +as intended. +Rules with both +.Cm record-state +and +.Cm defer-immediate-action +create a dynamic rule and continue with the next rule without actually +performing the action part of this rule. When the rule is later activated +via the state table, the action is performed as usual. .It Cm diverted Matches only packets generated by a divert socket. .It Cm diverted-loopback @@ -1698,6 +1732,14 @@ option is used, in which case symbolic resolution will be attempted). .It Cm proto Ar protocol Matches packets with the corresponding IP protocol. +.It Cm record-state +Upon a match, the firewall will create a dynamic rule as if +.Cm keep-state +was specified. +However, this option doesn't imply an implicit +.Cm check-state +in contrast to +.Cm keep-state . .It Cm recv | xmit | via Brq Ar ifX | Ar if Ns Cm * | Ar table Ns Po Ar name Ns Oo , Ns Ar value Oc Pc | Ar ipno | Ar any Matches packets received, transmitted or going through, respectively, the interface specified by exact name @@ -1746,6 +1788,12 @@ originating from the local host have no receive interface, while packets destined for the local host have no transmit interface. +.It Cm set-limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N +Works like +.Cm limit +but does not have an implicit +.Cm check-state +attached to it. .It Cm setup Matches TCP packets that have the SYN bit set but no ACK bit. This is the short form of @@ -2202,16 +2250,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 @@ -3396,6 +3446,15 @@ ruleset to minimize the amount of work scanning the ruleset. Your mileage may vary. .Pp +For more complex scenarios with dynamic rules +.Cm record-state +and +.Cm defer-action +can be used to precisely control creation and checking of dynamic rules. +Example of usage of these options are provided in +.Sx NETWORK ADDRESS TRANSLATION (NAT) +Section. +.Pp To limit the number of connections a user can open you can use the following type of rules: .Pp @@ -3662,6 +3721,40 @@ .Dl " 10.0.0.100" .Dl "ipfw nat 5 config redirect_port tcp" .Dl " 192.168.0.1:80,192.168.0.10:22,192.168.0.20:25 500" +.Pp +Sometimes you may want to mix NAT and dynamic rules. It could be achived with +.Cm record-state +and +.Cm defer-action +options. Problem is, you need to create dynamic rule before NAT and check it +after NAT actions (or vice versa) to have consistent addresses and ports. +Rule with +.Cm keep-state +option will trigger activation of existing dynamic state, and action of such +rule will be performed as soon as rule is matched. In case of NAT and +.Cm allow +rule packet need to be passed to NAT, not allowed as soon is possible. +.Pp +There is example of set of rules to achive this. Bear in mind that this +is exmaple only and it is not very usefult by itself. +.Pp +On way out, after all checks place this rules: +.Pp +.Dl "ipfw add allow record-state skip-action" +.Dl "ipfw add nat 1" +.Pp +And on way in there should be something like this: +.Pp +.Dl "ipfw add nat 1" +.Dl "ipfw add check-state" +.Pp +Please note, that first rule on way out doesn't allow packet and doesn't +execute existing dynamic rules. All it does, create new dynamic rule with +.Cm allow +action, if it is not created yet. Later, this dynamic rule is used on way +in by +.Cm check-state +rule. .Sh SEE ALSO .Xr cpp 1 , .Xr m4 1 , Index: sbin/ipfw/ipfw2.h =================================================================== --- sbin/ipfw/ipfw2.h +++ sbin/ipfw/ipfw2.h @@ -116,7 +116,9 @@ TOK_JAIL, TOK_IN, TOK_LIMIT, + TOK_SETLIMIT, TOK_KEEPSTATE, + TOK_RECORDSTATE, TOK_LAYER2, TOK_OUT, TOK_DIVERTED, @@ -261,6 +263,8 @@ TOK_INTPREFIX, TOK_EXTPREFIX, TOK_PREFIXLEN, + + TOK_SKIPACTION, }; /* Index: sbin/ipfw/ipfw2.c =================================================================== --- sbin/ipfw/ipfw2.c +++ sbin/ipfw/ipfw2.c @@ -297,7 +297,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 }, @@ -360,6 +362,8 @@ { "src-ip6", TOK_SRCIP6}, { "lookup", TOK_LOOKUP}, { "flow", TOK_FLOW}, + { "defer-action", TOK_SKIPACTION }, + { "defer-immediate-action", TOK_SKIPACTION }, { "//", TOK_COMMENT }, { "not", TOK_NOT }, /* pseudo option */ @@ -1411,6 +1415,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 */ @@ -1734,6 +1739,7 @@ break; /* done already */ case O_PROBE_STATE: + have_probe_state = 1; break; /* no need to print anything here */ case O_IP_SRC: @@ -2071,7 +2077,10 @@ break; case O_KEEP_STATE: - bprintf(bp, " keep-state"); + if (have_probe_state) + bprintf(bp, " keep-state"); + else + bprintf(bp, " record-state"); bprintf(bp, " %s", object_search_ctlv(fo->tstate, cmd->arg1, IPFW_TLV_STATE_NAME)); @@ -2083,7 +2092,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; @@ -2122,6 +2135,10 @@ 0, O_TAGGED); break; + case O_SKIP_ACTION: + bprintf(bp, " defer-immediate-action"); + break; + default: bprintf(bp, " [opcode %d len %d]", cmd->opcode, cmd->len); @@ -3642,8 +3659,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; @@ -4554,12 +4573,13 @@ case TOK_KEEPSTATE: { uint16_t uidx; + 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"); if (*av == NULL || match_token(rule_options, *av) != -1) { if (*av != NULL) @@ -4577,21 +4597,24 @@ av++; } have_state = cmd; + have_rstate = i == TOK_RECORDSTATE; fill_cmd(cmd, O_KEEP_STATE, 0, uidx); 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; @@ -4791,6 +4814,7 @@ av++; } break; + case TOK_FLOW: NEED1("missing table name"); if (strncmp(*av, "table(", 6) != 0) @@ -4800,6 +4824,14 @@ av++; break; + case TOK_SKIPACTION: + if (have_skipcmd) + errx(EX_USAGE, "only one defer-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); } @@ -4810,6 +4842,11 @@ } done: + + if (!have_state && have_skipcmd) + warnx("Rule contains \"defer-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 @@ -4832,12 +4869,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, have_state->arg1); 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); @@ -4848,6 +4888,7 @@ case O_LIMIT: case O_ALTQ: case O_TAG: + case O_SKIP_ACTION: break; default: bcopy(src, dst, i * sizeof(uint32_t)); @@ -4864,7 +4905,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; @@ -5385,7 +5437,3 @@ free(olh); } - - - - Index: sys/netinet/ip_fw.h =================================================================== --- sys/netinet/ip_fw.h +++ sys/netinet/ip_fw.h @@ -262,6 +262,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_EXTERNAL_ACTION, /* arg1=id of external action handler */ O_EXTERNAL_INSTANCE, /* arg1=id of eaction handler instance */ Index: sys/netpfil/ipfw/ip_fw2.c =================================================================== --- sys/netpfil/ipfw/ip_fw2.c +++ sys/netpfil/ipfw/ip_fw2.c @@ -2079,7 +2079,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 @@ -2096,11 +2098,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 a real 'action' + * either, and is stored right before the 'action' + * part of the rule, right after the O_KEEP_STATE + * opcode. It causes match failure so the real + * 'action' could be executed only if the rule + * is checked via dynamic rule from the state + * table, as in such case execution starts + * from the 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 */ @@ -2175,6 +2188,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 */ Index: sys/netpfil/ipfw/ip_fw_dynamic.c =================================================================== --- sys/netpfil/ipfw/ip_fw_dynamic.c +++ sys/netpfil/ipfw/ip_fw_dynamic.c @@ -878,13 +878,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; @@ -896,8 +898,8 @@ IPFW_BUCK_LOCK(i); - q = lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL, cmd->o.arg1); - if (q != NULL) { /* should never occur */ + q = lookup_dyn_rule_locked(&args->f_id, i, NULL, tcp, cmd->o.arg1); + 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 @@ -186,8 +186,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, uint16_t kidx); 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 @@ -1722,6 +1722,7 @@ #endif case O_IP4: case O_TAG: + case O_SKIP_ACTION: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; break;