mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-26 11:47:31 +00:00
Add "record-state", "set-limit" and "defer-action" rule options to ipfw.
"record-state" is similar to "keep-state", but it doesn't produce implicit O_PROBE_STATE opcode in a rule. "set-limit" is like "limit", but it has the same feature as "record-state", it is single opcode without implicit O_PROBE_STATE opcode. "defer-action" is targeted to be used with dynamic states. When rule with this opcode is matched, the rule's action will not be executed, instead dynamic state will be created. And when this state will be matched by "check-state", then rule action will be executed. This allows create a more complicated rulesets. Submitted by: lev MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D1776
This commit is contained in:
parent
98a8fdf6da
commit
f7c4fdee1a
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=336132
109
sbin/ipfw/ipfw.8
109
sbin/ipfw/ipfw.8
@ -1,7 +1,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd July 3, 2018
|
||||
.Dd July 9, 2018
|
||||
.Dt IPFW 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -212,9 +212,11 @@ or
|
||||
depending on how the kernel is configured.
|
||||
.Pp
|
||||
If the ruleset includes one or more rules with the
|
||||
.Cm keep-state
|
||||
or
|
||||
.Cm keep-state ,
|
||||
.Cm record-state ,
|
||||
.Cm limit
|
||||
or
|
||||
.Cm set-limit
|
||||
option,
|
||||
the firewall will have a
|
||||
.Em stateful
|
||||
@ -231,6 +233,18 @@ or
|
||||
.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
|
||||
@ -628,7 +642,12 @@ to simulate the effect of multiple paths leading to out-of-order
|
||||
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
|
||||
@ -1462,6 +1481,21 @@ action followed by the comment.
|
||||
.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
|
||||
@ -1780,6 +1814,14 @@ and they are always printed as hexadecimal (unless the
|
||||
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
|
||||
@ -1828,6 +1870,12 @@ A packet might not have a receive or transmit interface: packets
|
||||
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
|
||||
@ -2284,16 +2332,18 @@ create rules for specific flows when packets that
|
||||
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
|
||||
or
|
||||
.Cm keep-state ,
|
||||
.Cm record-state ,
|
||||
.Cm limit
|
||||
or
|
||||
.Cm set-limit
|
||||
rule, causing the creation of a
|
||||
.Em dynamic
|
||||
rule which will match all and only packets with
|
||||
@ -3665,6 +3715,15 @@ rule should usually be placed near the beginning of the
|
||||
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
|
||||
@ -3931,6 +3990,40 @@ or it could be split in:
|
||||
.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 ,
|
||||
|
@ -305,7 +305,9 @@ static struct _s_x rule_options[] = {
|
||||
{ "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 },
|
||||
@ -368,6 +370,8 @@ static struct _s_x rule_options[] = {
|
||||
{ "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 */
|
||||
@ -1371,9 +1375,10 @@ struct show_state {
|
||||
const ipfw_insn *eaction;
|
||||
uint8_t *printed;
|
||||
int flags;
|
||||
#define HAVE_PROTO 0x0001
|
||||
#define HAVE_SRCIP 0x0002
|
||||
#define HAVE_DSTIP 0x0004
|
||||
#define HAVE_PROTO 0x0001
|
||||
#define HAVE_SRCIP 0x0002
|
||||
#define HAVE_DSTIP 0x0004
|
||||
#define HAVE_PROBE_STATE 0x0008
|
||||
int proto;
|
||||
int or_block;
|
||||
};
|
||||
@ -1415,13 +1420,12 @@ mark_printed(struct show_state *state, const ipfw_insn *cmd)
|
||||
}
|
||||
|
||||
static void
|
||||
print_limit(struct buf_pr *bp, const ipfw_insn_limit *limit)
|
||||
print_limit_mask(struct buf_pr *bp, const ipfw_insn_limit *limit)
|
||||
{
|
||||
struct _s_x *p = limit_masks;
|
||||
char const *comma = " ";
|
||||
uint8_t x;
|
||||
|
||||
bprintf(bp, " limit");
|
||||
for (x = limit->limit_mask; p->x != 0; p++) {
|
||||
if ((x & p->x) == p->x) {
|
||||
x &= ~p->x;
|
||||
@ -1455,6 +1459,7 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo,
|
||||
bprintf(bp, "prob %f ", d);
|
||||
break;
|
||||
case O_PROBE_STATE: /* no need to print anything here */
|
||||
state->flags |= HAVE_PROBE_STATE;
|
||||
break;
|
||||
case O_IP_SRC:
|
||||
case O_IP_SRC_LOOKUP:
|
||||
@ -1661,13 +1666,20 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo,
|
||||
bprintf(bp, " // %s", (char *)(cmd + 1));
|
||||
break;
|
||||
case O_KEEP_STATE:
|
||||
bprintf(bp, " keep-state");
|
||||
if (state->flags & 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));
|
||||
break;
|
||||
case O_LIMIT:
|
||||
print_limit(bp, insntod(cmd, limit));
|
||||
if (state->flags & HAVE_PROBE_STATE)
|
||||
bprintf(bp, " limit");
|
||||
else
|
||||
bprintf(bp, " set-limit");
|
||||
print_limit_mask(bp, insntod(cmd, limit));
|
||||
bprintf(bp, " :%s",
|
||||
object_search_ctlv(fo->tstate, cmd->arg1,
|
||||
IPFW_TLV_STATE_NAME));
|
||||
@ -1691,6 +1703,9 @@ print_instruction(struct buf_pr *bp, const struct format_opts *fo,
|
||||
print_newports(bp, insntod(cmd, u16),
|
||||
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);
|
||||
@ -3706,8 +3721,10 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
/*
|
||||
* 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;
|
||||
@ -4647,15 +4664,16 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
av++;
|
||||
break;
|
||||
|
||||
case TOK_KEEPSTATE: {
|
||||
case TOK_KEEPSTATE:
|
||||
case TOK_RECORDSTATE: {
|
||||
uint16_t uidx;
|
||||
|
||||
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 && *av[0] == ':') {
|
||||
if (state_check_name(*av + 1) != 0)
|
||||
errx(EX_DATAERR,
|
||||
@ -4667,21 +4685,24 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
uidx = pack_object(tstate, default_state_name,
|
||||
IPFW_TLV_STATE_NAME);
|
||||
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;
|
||||
@ -4884,6 +4905,14 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
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);
|
||||
}
|
||||
@ -4894,6 +4923,11 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
}
|
||||
|
||||
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
|
||||
@ -4916,12 +4950,15 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
/*
|
||||
* 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);
|
||||
@ -4932,6 +4969,7 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
case O_LIMIT:
|
||||
case O_ALTQ:
|
||||
case O_TAG:
|
||||
case O_SKIP_ACTION:
|
||||
break;
|
||||
default:
|
||||
bcopy(src, dst, i * sizeof(uint32_t));
|
||||
@ -4948,6 +4986,17 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate)
|
||||
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
|
||||
*/
|
||||
|
@ -124,7 +124,9 @@ enum tokens {
|
||||
TOK_JAIL,
|
||||
TOK_IN,
|
||||
TOK_LIMIT,
|
||||
TOK_SETLIMIT,
|
||||
TOK_KEEPSTATE,
|
||||
TOK_RECORDSTATE,
|
||||
TOK_LAYER2,
|
||||
TOK_OUT,
|
||||
TOK_DIVERTED,
|
||||
@ -294,6 +296,8 @@ enum tokens {
|
||||
TOK_PREFIXLEN,
|
||||
|
||||
TOK_TCPSETMSS,
|
||||
|
||||
TOK_SKIPACTION,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -285,6 +285,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
|
||||
O_EXTERNAL_INSTANCE, /* arg1=id of eaction handler instance */
|
||||
O_EXTERNAL_DATA, /* variable length data */
|
||||
|
||||
O_SKIP_ACTION, /* none */
|
||||
|
||||
O_LAST_OPCODE /* not an opcode! */
|
||||
};
|
||||
|
||||
|
@ -2584,7 +2584,9 @@ do { \
|
||||
*
|
||||
* 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
|
||||
@ -2601,6 +2603,16 @@ do { \
|
||||
* 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:
|
||||
@ -2653,6 +2665,11 @@ do { \
|
||||
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 */
|
||||
|
@ -1750,6 +1750,7 @@ check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci)
|
||||
#endif
|
||||
case O_IP4:
|
||||
case O_TAG:
|
||||
case O_SKIP_ACTION:
|
||||
if (cmdlen != F_INSN_SIZE(ipfw_insn))
|
||||
goto bad_size;
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user