Add new rule actions "call" and "return" to ipfw. They make
possible to organize subroutines with rules. The "call" action saves the current rule number in the internal stack and rules processing continues from the first rule with specified number (similar to skipto action). If later a rule with "return" action is encountered, the processing returns to the first rule with number of "call" rule saved in the stack plus one or higher. Submitted by: Vadim Goncharov Discussed by: ipfw@, luigi@
This commit is contained in:
parent
e6cf436883
commit
9527ec6e52
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=223666
@ -1,7 +1,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" $FreeBSD$
|
.\" $FreeBSD$
|
||||||
.\"
|
.\"
|
||||||
.Dd June 14, 2011
|
.Dd June 29, 2011
|
||||||
.Dt IPFW 8
|
.Dt IPFW 8
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@ -822,6 +822,78 @@ skipto, but care should be used, as no destination caching
|
|||||||
is possible in this case so the rules are always walked to find it,
|
is possible in this case so the rules are always walked to find it,
|
||||||
starting from the
|
starting from the
|
||||||
.Cm skipto .
|
.Cm skipto .
|
||||||
|
.It Cm call Ar number | tablearg
|
||||||
|
The current rule number is saved in the internal stack and
|
||||||
|
ruleset processing continues with the first rule numbered
|
||||||
|
.Ar number
|
||||||
|
or higher.
|
||||||
|
If later a rule with the
|
||||||
|
.Cm return
|
||||||
|
action is encountered, the processing returns to the first rule
|
||||||
|
with number of this
|
||||||
|
.Cm call
|
||||||
|
rule plus one or higher
|
||||||
|
(the same behaviour as with packets returning from
|
||||||
|
.Xr divert 4
|
||||||
|
socket after a
|
||||||
|
.Cm divert
|
||||||
|
action).
|
||||||
|
This could be used to make somewhat like an assembly language
|
||||||
|
.Dq subroutine
|
||||||
|
calls to rules with common checks for different interfaces, etc.
|
||||||
|
.Pp
|
||||||
|
Rule with any number could be called, not just forward jumps as with
|
||||||
|
.Cm skipto .
|
||||||
|
So, to prevent endless loops in case of mistakes, both
|
||||||
|
.Cm call
|
||||||
|
and
|
||||||
|
.Cm return
|
||||||
|
actions don't do any jumps and simply go to the next rule if memory
|
||||||
|
can't be allocated or stack overflowed/undeflowed.
|
||||||
|
.Pp
|
||||||
|
Internally stack for rule numbers is implemented using
|
||||||
|
.Xr mbuf_tags 9
|
||||||
|
facility and currently has size of 16 entries.
|
||||||
|
As mbuf tags are lost when packet leaves the kernel,
|
||||||
|
.Cm divert
|
||||||
|
should not be used in subroutines to avoid endless loops
|
||||||
|
and other undesired effects.
|
||||||
|
.It Cm return
|
||||||
|
Takes rule number saved to internal stack by the last
|
||||||
|
.Cm call
|
||||||
|
action and returns ruleset processing to the first rule
|
||||||
|
with number greater than number of corresponding
|
||||||
|
.Cm call
|
||||||
|
rule. See description of the
|
||||||
|
.Cm call
|
||||||
|
action for more details.
|
||||||
|
.Pp
|
||||||
|
Note that
|
||||||
|
.Cm return
|
||||||
|
rules usually end a
|
||||||
|
.Dq subroutine
|
||||||
|
and thus are unconditional, but
|
||||||
|
.Nm
|
||||||
|
command-line utility currently requires every action except
|
||||||
|
.Cm check-state
|
||||||
|
to have body.
|
||||||
|
While it is sometimes useful to return only on some packets,
|
||||||
|
usually you want to print just
|
||||||
|
.Dq return
|
||||||
|
for readability.
|
||||||
|
A workaround for this is to use new syntax and
|
||||||
|
.Fl c
|
||||||
|
switch:
|
||||||
|
.Pp
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
# Add a rule without actual body
|
||||||
|
ipfw add 2999 return via any
|
||||||
|
|
||||||
|
# List rules without "from any to any" part
|
||||||
|
ipfw -c list
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
|
This cosmetic annoyance may be fixed in future releases.
|
||||||
.It Cm tee Ar port
|
.It Cm tee Ar port
|
||||||
Send a copy of packets matching this rule to the
|
Send a copy of packets matching this rule to the
|
||||||
.Xr divert 4
|
.Xr divert 4
|
||||||
@ -3253,3 +3325,18 @@ for the respective conversations.
|
|||||||
To avoid failures of network error detection and path MTU discovery,
|
To avoid failures of network error detection and path MTU discovery,
|
||||||
ICMP error messages may need to be allowed explicitly through static
|
ICMP error messages may need to be allowed explicitly through static
|
||||||
rules.
|
rules.
|
||||||
|
.Pp
|
||||||
|
Rules using
|
||||||
|
.Cm call
|
||||||
|
and
|
||||||
|
.Cm return
|
||||||
|
actions may lead to confusing behaviour if ruleset has mistakes,
|
||||||
|
and/or interaction with other subsystems (netgraph, dummynet, etc.) is used.
|
||||||
|
One possible case for this is packet leaving
|
||||||
|
.Nm
|
||||||
|
in subroutine on the input pass, while later on output encountering unpaired
|
||||||
|
.Cm return
|
||||||
|
first.
|
||||||
|
As the call stack is kept intact after input pass, packet will suddenly
|
||||||
|
return to the rule number used on input pass, not on output one.
|
||||||
|
Order of processing should be checked carefully to avoid such mistakes.
|
||||||
|
@ -214,6 +214,8 @@ static struct _s_x rule_actions[] = {
|
|||||||
{ "nat", TOK_NAT },
|
{ "nat", TOK_NAT },
|
||||||
{ "reass", TOK_REASS },
|
{ "reass", TOK_REASS },
|
||||||
{ "setfib", TOK_SETFIB },
|
{ "setfib", TOK_SETFIB },
|
||||||
|
{ "call", TOK_CALL },
|
||||||
|
{ "return", TOK_RETURN },
|
||||||
{ NULL, 0 } /* terminator */
|
{ NULL, 0 } /* terminator */
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1136,6 +1138,13 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
|
|||||||
printf("reass");
|
printf("reass");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case O_CALLRETURN:
|
||||||
|
if (cmd->len & F_NOT)
|
||||||
|
printf("return");
|
||||||
|
else
|
||||||
|
PRINT_UINT_ARG("call ", cmd->arg1);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf("** unrecognized action %d len %d ",
|
printf("** unrecognized action %d len %d ",
|
||||||
cmd->opcode, cmd->len);
|
cmd->opcode, cmd->len);
|
||||||
@ -2771,6 +2780,9 @@ ipfw_add(char *av[])
|
|||||||
goto chkarg;
|
goto chkarg;
|
||||||
case TOK_TEE:
|
case TOK_TEE:
|
||||||
action->opcode = O_TEE;
|
action->opcode = O_TEE;
|
||||||
|
goto chkarg;
|
||||||
|
case TOK_CALL:
|
||||||
|
action->opcode = O_CALLRETURN;
|
||||||
chkarg:
|
chkarg:
|
||||||
if (!av[0])
|
if (!av[0])
|
||||||
errx(EX_USAGE, "missing argument for %s", *(av - 1));
|
errx(EX_USAGE, "missing argument for %s", *(av - 1));
|
||||||
@ -2863,6 +2875,10 @@ ipfw_add(char *av[])
|
|||||||
action->opcode = O_REASS;
|
action->opcode = O_REASS;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TOK_RETURN:
|
||||||
|
fill_cmd(action, O_CALLRETURN, F_NOT, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errx(EX_DATAERR, "invalid action %s\n", av[-1]);
|
errx(EX_DATAERR, "invalid action %s\n", av[-1]);
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,8 @@ enum tokens {
|
|||||||
TOK_CHECKSTATE,
|
TOK_CHECKSTATE,
|
||||||
TOK_NAT,
|
TOK_NAT,
|
||||||
TOK_REASS,
|
TOK_REASS,
|
||||||
|
TOK_CALL,
|
||||||
|
TOK_RETURN,
|
||||||
|
|
||||||
TOK_ALTQ,
|
TOK_ALTQ,
|
||||||
TOK_LOG,
|
TOK_LOG,
|
||||||
|
@ -56,6 +56,12 @@
|
|||||||
#define IPFW_ARG_MAX 65534
|
#define IPFW_ARG_MAX 65534
|
||||||
#define IP_FW_TABLEARG 65535 /* XXX should use 0 */
|
#define IP_FW_TABLEARG 65535 /* XXX should use 0 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of entries in the call stack of the call/return commands.
|
||||||
|
* Call stack currently is an uint16_t array with rule numbers.
|
||||||
|
*/
|
||||||
|
#define IPFW_CALLSTACK_SIZE 16
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The kernel representation of ipfw rules is made of a list of
|
* The kernel representation of ipfw rules is made of a list of
|
||||||
* 'instructions' (for all practical purposes equivalent to BPF
|
* 'instructions' (for all practical purposes equivalent to BPF
|
||||||
@ -195,6 +201,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
|
|||||||
|
|
||||||
O_SOCKARG, /* socket argument */
|
O_SOCKARG, /* socket argument */
|
||||||
|
|
||||||
|
O_CALLRETURN, /* arg1=called rule number */
|
||||||
|
|
||||||
O_LAST_OPCODE /* not an opcode! */
|
O_LAST_OPCODE /* not an opcode! */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -286,6 +286,7 @@ enum {
|
|||||||
};
|
};
|
||||||
#define MTAG_IPFW 1148380143 /* IPFW-tagged cookie */
|
#define MTAG_IPFW 1148380143 /* IPFW-tagged cookie */
|
||||||
#define MTAG_IPFW_RULE 1262273568 /* rule reference */
|
#define MTAG_IPFW_RULE 1262273568 /* rule reference */
|
||||||
|
#define MTAG_IPFW_CALL 1308397630 /* call stack */
|
||||||
|
|
||||||
struct ip_fw_args;
|
struct ip_fw_args;
|
||||||
typedef int (*ip_fw_chk_ptr_t)(struct ip_fw_args *args);
|
typedef int (*ip_fw_chk_ptr_t)(struct ip_fw_args *args);
|
||||||
|
@ -2095,6 +2095,123 @@ do { \
|
|||||||
continue;
|
continue;
|
||||||
break; /* not reached */
|
break; /* not reached */
|
||||||
|
|
||||||
|
case O_CALLRETURN: {
|
||||||
|
/*
|
||||||
|
* Implementation of `subroutine' call/return,
|
||||||
|
* in the stack carried in an mbuf tag. This
|
||||||
|
* is different from `skipto' in that any call
|
||||||
|
* address is possible (`skipto' must prevent
|
||||||
|
* backward jumps to avoid endless loops).
|
||||||
|
* We have `return' action when F_NOT flag is
|
||||||
|
* present. The `m_tag_id' field is used as
|
||||||
|
* stack pointer.
|
||||||
|
*/
|
||||||
|
struct m_tag *mtag;
|
||||||
|
uint16_t jmpto, *stack;
|
||||||
|
|
||||||
|
#define IS_CALL ((cmd->len & F_NOT) == 0)
|
||||||
|
#define IS_RETURN ((cmd->len & F_NOT) != 0)
|
||||||
|
/*
|
||||||
|
* Hand-rolled version of m_tag_locate() with
|
||||||
|
* wildcard `type'.
|
||||||
|
* If not already tagged, allocate new tag.
|
||||||
|
*/
|
||||||
|
mtag = m_tag_first(m);
|
||||||
|
while (mtag != NULL) {
|
||||||
|
if (mtag->m_tag_cookie ==
|
||||||
|
MTAG_IPFW_CALL)
|
||||||
|
break;
|
||||||
|
mtag = m_tag_next(m, mtag);
|
||||||
|
}
|
||||||
|
if (mtag == NULL && IS_CALL) {
|
||||||
|
mtag = m_tag_alloc(MTAG_IPFW_CALL, 0,
|
||||||
|
IPFW_CALLSTACK_SIZE *
|
||||||
|
sizeof(uint16_t), M_NOWAIT);
|
||||||
|
if (mtag != NULL)
|
||||||
|
m_tag_prepend(m, mtag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On error both `call' and `return' just
|
||||||
|
* continue with next rule.
|
||||||
|
*/
|
||||||
|
if (IS_RETURN && (mtag == NULL ||
|
||||||
|
mtag->m_tag_id == 0)) {
|
||||||
|
l = 0; /* exit inner loop */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (IS_CALL && (mtag == NULL ||
|
||||||
|
mtag->m_tag_id >= IPFW_CALLSTACK_SIZE)) {
|
||||||
|
printf("ipfw: call stack error, "
|
||||||
|
"go to next rule\n");
|
||||||
|
l = 0; /* exit inner loop */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->pcnt++; /* update stats */
|
||||||
|
f->bcnt += pktlen;
|
||||||
|
f->timestamp = time_uptime;
|
||||||
|
stack = (uint16_t *)(mtag + 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The `call' action may use cached f_pos
|
||||||
|
* (in f->next_rule), whose version is written
|
||||||
|
* in f->next_rule.
|
||||||
|
* The `return' action, however, doesn't have
|
||||||
|
* fixed jump address in cmd->arg1 and can't use
|
||||||
|
* cache.
|
||||||
|
*/
|
||||||
|
if (IS_CALL) {
|
||||||
|
stack[mtag->m_tag_id] = f->rulenum;
|
||||||
|
mtag->m_tag_id++;
|
||||||
|
if (cmd->arg1 != IP_FW_TABLEARG &&
|
||||||
|
(uintptr_t)f->x_next == chain->id) {
|
||||||
|
f_pos = (uintptr_t)f->next_rule;
|
||||||
|
} else {
|
||||||
|
jmpto = (cmd->arg1 ==
|
||||||
|
IP_FW_TABLEARG) ? tablearg:
|
||||||
|
cmd->arg1;
|
||||||
|
f_pos = ipfw_find_rule(chain,
|
||||||
|
jmpto, 0);
|
||||||
|
/* update the cache */
|
||||||
|
if (cmd->arg1 !=
|
||||||
|
IP_FW_TABLEARG) {
|
||||||
|
f->next_rule =
|
||||||
|
(void *)(uintptr_t)
|
||||||
|
f_pos;
|
||||||
|
f->x_next =
|
||||||
|
(void *)(uintptr_t)
|
||||||
|
chain->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { /* `return' action */
|
||||||
|
mtag->m_tag_id--;
|
||||||
|
jmpto = stack[mtag->m_tag_id] + 1;
|
||||||
|
f_pos = ipfw_find_rule(chain, jmpto, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip disabled rules, and re-enter
|
||||||
|
* the inner loop with the correct
|
||||||
|
* f_pos, f, l and cmd.
|
||||||
|
* Also clear cmdlen and skip_or
|
||||||
|
*/
|
||||||
|
for (; f_pos < chain->n_rules - 1 &&
|
||||||
|
(V_set_disable &
|
||||||
|
(1 << chain->map[f_pos]->set)); f_pos++)
|
||||||
|
;
|
||||||
|
/* Re-enter the inner loop at the dest rule. */
|
||||||
|
f = chain->map[f_pos];
|
||||||
|
l = f->cmd_len;
|
||||||
|
cmd = f->cmd;
|
||||||
|
cmdlen = 0;
|
||||||
|
skip_or = 0;
|
||||||
|
continue;
|
||||||
|
break; /* NOTREACHED */
|
||||||
|
}
|
||||||
|
#undef IS_CALL
|
||||||
|
#undef IS_RETURN
|
||||||
|
|
||||||
case O_REJECT:
|
case O_REJECT:
|
||||||
/*
|
/*
|
||||||
* Drop the packet and send a reject notice
|
* Drop the packet and send a reject notice
|
||||||
|
@ -304,6 +304,13 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
|
|||||||
case O_REASS:
|
case O_REASS:
|
||||||
action = "Reass";
|
action = "Reass";
|
||||||
break;
|
break;
|
||||||
|
case O_CALLRETURN:
|
||||||
|
if (cmd->len & F_NOT)
|
||||||
|
action = "Return";
|
||||||
|
else
|
||||||
|
snprintf(SNPARGS(action2, 0), "Call %d",
|
||||||
|
cmd->arg1);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
action = "UNKNOWN";
|
action = "UNKNOWN";
|
||||||
break;
|
break;
|
||||||
|
@ -752,6 +752,7 @@ check_ipfw_struct(struct ip_fw *rule, int size)
|
|||||||
#endif
|
#endif
|
||||||
case O_SKIPTO:
|
case O_SKIPTO:
|
||||||
case O_REASS:
|
case O_REASS:
|
||||||
|
case O_CALLRETURN:
|
||||||
check_size:
|
check_size:
|
||||||
if (cmdlen != F_INSN_SIZE(ipfw_insn))
|
if (cmdlen != F_INSN_SIZE(ipfw_insn))
|
||||||
goto bad_size;
|
goto bad_size;
|
||||||
|
Loading…
Reference in New Issue
Block a user