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:
Andrey V. Elsukov 2011-06-29 10:06:58 +00:00
parent e6cf436883
commit 9527ec6e52
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=223666
8 changed files with 240 additions and 1 deletions

View File

@ -1,7 +1,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 14, 2011
.Dd June 29, 2011
.Dt IPFW 8
.Os
.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,
starting from the
.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
Send a copy of packets matching this rule to the
.Xr divert 4
@ -3253,3 +3325,18 @@ for the respective conversations.
To avoid failures of network error detection and path MTU discovery,
ICMP error messages may need to be allowed explicitly through static
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.

View File

@ -214,6 +214,8 @@ static struct _s_x rule_actions[] = {
{ "nat", TOK_NAT },
{ "reass", TOK_REASS },
{ "setfib", TOK_SETFIB },
{ "call", TOK_CALL },
{ "return", TOK_RETURN },
{ NULL, 0 } /* terminator */
};
@ -1136,6 +1138,13 @@ show_ipfw(struct ip_fw *rule, int pcwidth, int bcwidth)
printf("reass");
break;
case O_CALLRETURN:
if (cmd->len & F_NOT)
printf("return");
else
PRINT_UINT_ARG("call ", cmd->arg1);
break;
default:
printf("** unrecognized action %d len %d ",
cmd->opcode, cmd->len);
@ -2771,6 +2780,9 @@ ipfw_add(char *av[])
goto chkarg;
case TOK_TEE:
action->opcode = O_TEE;
goto chkarg;
case TOK_CALL:
action->opcode = O_CALLRETURN;
chkarg:
if (!av[0])
errx(EX_USAGE, "missing argument for %s", *(av - 1));
@ -2863,6 +2875,10 @@ ipfw_add(char *av[])
action->opcode = O_REASS;
break;
case TOK_RETURN:
fill_cmd(action, O_CALLRETURN, F_NOT, 0);
break;
default:
errx(EX_DATAERR, "invalid action %s\n", av[-1]);
}

View File

@ -99,6 +99,8 @@ enum tokens {
TOK_CHECKSTATE,
TOK_NAT,
TOK_REASS,
TOK_CALL,
TOK_RETURN,
TOK_ALTQ,
TOK_LOG,

View File

@ -56,6 +56,12 @@
#define IPFW_ARG_MAX 65534
#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
* 'instructions' (for all practical purposes equivalent to BPF
@ -195,6 +201,8 @@ enum ipfw_opcodes { /* arguments (4 byte each) */
O_SOCKARG, /* socket argument */
O_CALLRETURN, /* arg1=called rule number */
O_LAST_OPCODE /* not an opcode! */
};

View File

@ -286,6 +286,7 @@ enum {
};
#define MTAG_IPFW 1148380143 /* IPFW-tagged cookie */
#define MTAG_IPFW_RULE 1262273568 /* rule reference */
#define MTAG_IPFW_CALL 1308397630 /* call stack */
struct ip_fw_args;
typedef int (*ip_fw_chk_ptr_t)(struct ip_fw_args *args);

View File

@ -2095,6 +2095,123 @@ do { \
continue;
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:
/*
* Drop the packet and send a reject notice

View File

@ -304,6 +304,13 @@ ipfw_log(struct ip_fw *f, u_int hlen, struct ip_fw_args *args,
case O_REASS:
action = "Reass";
break;
case O_CALLRETURN:
if (cmd->len & F_NOT)
action = "Return";
else
snprintf(SNPARGS(action2, 0), "Call %d",
cmd->arg1);
break;
default:
action = "UNKNOWN";
break;

View File

@ -752,6 +752,7 @@ check_ipfw_struct(struct ip_fw *rule, int size)
#endif
case O_SKIPTO:
case O_REASS:
case O_CALLRETURN:
check_size:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
goto bad_size;