diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 4a223203f7d2..6fc12c82c132 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -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. diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index 64e5eb1e9f20..e259f5ec8091 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -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]); } diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h index 9562f324432e..bade0dda6529 100644 --- a/sbin/ipfw/ipfw2.h +++ b/sbin/ipfw/ipfw2.h @@ -99,6 +99,8 @@ enum tokens { TOK_CHECKSTATE, TOK_NAT, TOK_REASS, + TOK_CALL, + TOK_RETURN, TOK_ALTQ, TOK_LOG, diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index 06e107c2de61..ff3a67fe61ce 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -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! */ }; diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h index e993279199da..cd30093e1700 100644 --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -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); diff --git a/sys/netinet/ipfw/ip_fw2.c b/sys/netinet/ipfw/ip_fw2.c index 48837a7f8f17..4e25f9a086d8 100644 --- a/sys/netinet/ipfw/ip_fw2.c +++ b/sys/netinet/ipfw/ip_fw2.c @@ -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 diff --git a/sys/netinet/ipfw/ip_fw_log.c b/sys/netinet/ipfw/ip_fw_log.c index 3560e137f1e5..2b55a382d70d 100644 --- a/sys/netinet/ipfw/ip_fw_log.c +++ b/sys/netinet/ipfw/ip_fw_log.c @@ -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; diff --git a/sys/netinet/ipfw/ip_fw_sockopt.c b/sys/netinet/ipfw/ip_fw_sockopt.c index 2347456a8c22..143285850113 100644 --- a/sys/netinet/ipfw/ip_fw_sockopt.c +++ b/sys/netinet/ipfw/ip_fw_sockopt.c @@ -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;