Index: sbin/ipfw/ipfw2.c =================================================================== --- sbin/ipfw/ipfw2.c (revision 321516) +++ sbin/ipfw/ipfw2.c (working copy) @@ -240,6 +240,8 @@ static struct _s_x rule_eactions[] = { { "nat64stl", TOK_NAT64STL }, { "nptv6", TOK_NPTV6 }, { "tcp-setmss", TOK_TCPSETMSS }, + { "nat-in", TOK_NATIN }, + { "nat-out", TOK_NATOUT }, { NULL, 0 } /* terminator */ }; @@ -275,6 +277,8 @@ static struct _s_x rule_actions[] = { { "return", TOK_RETURN }, { "eaction", TOK_EACTION }, { "tcp-setmss", TOK_TCPSETMSS }, + { "nat-in", TOK_NATIN }, + { "nat-out", TOK_NATOUT }, { NULL, 0 } /* terminator */ }; @@ -1646,12 +1650,24 @@ show_static_rule(struct cmdline_opts *co, struct f } case O_EXTERNAL_DATA: { + int tok; if (has_eaction == NULL) break; + + ename = object_search_ctlv(fo->tstate, + has_eaction->arg1, IPFW_TLV_EACTION); + tok = match_token(rule_eactions, ename); + if (tok == TOK_NATIN || tok == TOK_NATOUT) { + if (cmd->arg1 != IP_FW_NAT44_GLOBAL) + bprint_uint_arg(bp, " ", + cmd->arg1); + else + bprintf(bp, " global"); + break; + } /* - * Currently we support data formatting only for - * external data with datalen u16. For unknown data - * print its size in bytes. + * For unknown data print its size in bytes, + * and print arg1 for external data with u16 datalen. */ if (cmd->len == F_INSN_SIZE(ipfw_insn)) bprintf(bp, " %u", cmd->arg1); @@ -4030,6 +4046,27 @@ chkarg: break; } + case TOK_NATIN: + case TOK_NATOUT: { + uint16_t idx; + + idx = pack_object(tstate, *(av - 1), IPFW_TLV_EACTION); + if (idx == 0) + errx(EX_DATAERR, "pack_object failed"); + fill_cmd(action, O_EXTERNAL_ACTION, 0, idx); + NEED1("Missing NAT instance"); + action = next_cmd(action, &ablen); + action->opcode = O_EXTERNAL_DATA; + action->len = F_INSN_SIZE(ipfw_insn_nat); + CHECK_ACTLEN; + if (_substrcmp(*av, "global") == 0) { + action->arg1 = IP_FW_NAT44_GLOBAL; + av++; + } else + goto chkarg; + break; + } + default: av--; if (match_token(rule_eactions, *av) == -1) Index: sbin/ipfw/ipfw2.h =================================================================== --- sbin/ipfw/ipfw2.h (revision 321516) +++ sbin/ipfw/ipfw2.h (working copy) @@ -101,6 +101,8 @@ enum tokens { TOK_UNREACH, TOK_CHECKSTATE, TOK_NAT, + TOK_NATIN, + TOK_NATOUT, TOK_REASS, TOK_CALL, TOK_RETURN, Index: sys/netpfil/ipfw/ip_fw2.c =================================================================== --- sys/netpfil/ipfw/ip_fw2.c (revision 321516) +++ sys/netpfil/ipfw/ip_fw2.c (working copy) @@ -2608,6 +2608,12 @@ do { \ } case O_EXTERNAL_ACTION: l = 0; /* in any case exit inner loop */ + /* + * Save current tablearg value in the + * @args, it may be used by external action. + */ + if (tablearg != 0) + args->tablearg = tablearg; retval = ipfw_run_eaction(chain, args, cmd, &done); /* @@ -2626,6 +2632,12 @@ do { \ */ dyn_dir = MATCH_UNKNOWN; } + /* + * If @done is non-zero, save the result of + * match in case we reenter the rules again. + */ + if (done != 0) + set_match(args, f_pos, chain); break; default: Index: sys/netpfil/ipfw/ip_fw_nat.c =================================================================== --- sys/netpfil/ipfw/ip_fw_nat.c (revision 321516) +++ sys/netpfil/ipfw/ip_fw_nat.c (working copy) @@ -55,6 +55,17 @@ __FBSDID("$FreeBSD$"); #include /* XXX for in_cksum */ +static VNET_DEFINE(uint16_t, natin_eid) = 0; +static VNET_DEFINE(uint16_t, natout_eid) = 0; +#define V_natin_eid VNET(natin_eid) +#define V_natout_eid VNET(natout_eid) + +enum ipfw_nat_direction { + UNKNOWN = 0, + INBOUND, + OUTBOUND +}; + struct cfg_spool { LIST_ENTRY(cfg_spool) _next; /* chain of spool instances */ struct in_addr addr; @@ -279,7 +290,6 @@ free_nat_instance(struct cfg_nat *ptr) free(ptr, M_IPFW); } - /* * ipfw_nat - perform mbuf header translation. * @@ -288,7 +298,8 @@ free_nat_instance(struct cfg_nat *ptr) * */ static int -ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m) +ipfw_nat_dir(struct ip_fw_args *args, struct cfg_nat *t, + enum ipfw_nat_direction dir) { struct mbuf *mcl; struct ip *ip; @@ -299,7 +310,7 @@ static int ldt = 0; retval = 0; - mcl = m_megapullup(m, m->m_pkthdr.len); + mcl = m_megapullup(args->m, args->m->m_pkthdr.len); if (mcl == NULL) { args->m = NULL; return (IP_FW_DENY); @@ -346,7 +357,7 @@ static int /* Check if this is 'global' instance */ if (t == NULL) { - if (args->oif == NULL) { + if (args->oif == NULL && dir != OUTBOUND) { /* Wrong direction, skip processing */ args->m = mcl; return (IP_FW_NAT); @@ -373,7 +384,8 @@ static int return (IP_FW_NAT); } } else { - if (args->oif == NULL) + if (dir == INBOUND || + (dir != OUTBOUND && args->oif == NULL)) retval = LibAliasIn(t->lib, c, mcl->m_len + M_TRAILINGSPACE(mcl)); else @@ -389,8 +401,9 @@ static int * b) libalias returns PKT_ALIAS_IGNORED and * PKT_ALIAS_DENY_INCOMING flag is set. */ - if (retval == PKT_ALIAS_ERROR || - (args->oif == NULL && (retval == PKT_ALIAS_UNRESOLVED_FRAGMENT || + if (retval == PKT_ALIAS_ERROR || ((dir == INBOUND || + (dir != OUTBOUND && args->oif == NULL)) && + (retval == PKT_ALIAS_UNRESOLVED_FRAGMENT || (retval == PKT_ALIAS_IGNORED && (t->mode & PKT_ALIAS_DENY_INCOMING) != 0)))) { /* XXX - should i add some logging? */ @@ -455,6 +468,14 @@ static int return (IP_FW_NAT); } +static int +ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m) +{ + + MPASS(m == args->m); + return (ipfw_nat_dir(args, t, UNKNOWN)); +} + static struct cfg_nat * lookup_nat(struct nat_list *l, int nat_id) { @@ -1135,9 +1156,57 @@ ipfw_nat_get_log(struct sockopt *sopt) } static int +ipfw_nat_ea(struct ip_fw_chain *chain, struct ip_fw_args *args, + ipfw_insn *cmd, int *done) +{ + struct cfg_nat *t; + ipfw_insn_nat *icmd; + int ret, nat_id; + + *done = 1; /* drop if not matched */ + ret = IP_FW_DENY; + icmd = (ipfw_insn_nat *)(cmd + 1); + if (cmd->opcode != O_EXTERNAL_ACTION || + (cmd->arg1 != V_natin_eid && cmd->arg1 != V_natout_eid) || + icmd->o.opcode != O_EXTERNAL_DATA || + icmd->o.len != F_INSN_SIZE(ipfw_insn_nat)) + return (ret); + + /* Follow the behaviour of O_NAT opcode handling */ + if (icmd->o.arg1 == IP_FW_NAT44_GLOBAL) { + if (cmd->arg1 != V_natout_eid) + return (IP_FW_NAT); + return (ipfw_nat_dir(args, NULL, OUTBOUND)); + } + t = icmd->nat; + if (t == NULL) { + nat_id = (icmd->o.arg1 == IP_FW_TARG) ? + TARG_VAL(chain, args->tablearg, nat): icmd->o.arg1; + t = lookup_nat(&chain->nat, nat_id); + if (t == NULL) + return (ret); + if (icmd->o.arg1 != IP_FW_TARG) + icmd->nat = t; + } + return (ipfw_nat_dir(args, t, + cmd->arg1 == V_natin_eid ? INBOUND: OUTBOUND)); +} + +static int vnet_ipfw_nat_init(const void *arg __unused) { + V_natin_eid = ipfw_add_eaction(&V_layer3_chain, ipfw_nat_ea, + "nat-in"); + if (V_natin_eid == 0) + return (ENXIO); + V_natout_eid = ipfw_add_eaction(&V_layer3_chain, ipfw_nat_ea, + "nat-out"); + if (V_natout_eid == 0) { + ipfw_del_eaction(&V_layer3_chain, V_natin_eid); + V_natin_eid = 0; + return (ENXIO); + } V_ipfw_nat_ready = 1; return (0); } @@ -1149,6 +1218,10 @@ vnet_ipfw_nat_uninit(const void *arg __unused) struct ip_fw_chain *chain; chain = &V_layer3_chain; + ipfw_del_eaction(chain, V_natin_eid); + ipfw_del_eaction(chain, V_natout_eid); + V_natin_eid = V_natout_eid = 0; + IPFW_WLOCK(chain); V_ipfw_nat_ready = 0; LIST_FOREACH_SAFE(ptr, &chain->nat, _next, ptr_temp) { Index: sys/netpfil/ipfw/ip_fw_private.h =================================================================== --- sys/netpfil/ipfw/ip_fw_private.h (revision 321516) +++ sys/netpfil/ipfw/ip_fw_private.h (working copy) @@ -106,6 +106,8 @@ struct ip_fw_args { struct sockaddr_in hopstore; struct sockaddr_in6 hopstore6; }; + /* tablearg value, may be used by external actions */ + uint32_t tablearg; }; MALLOC_DECLARE(M_IPFW);