--- sbin/ipfw/ipfw2.h.orig 2020-01-03 15:01:57.532554000 +0700 +++ sbin/ipfw/ipfw2.h 2020-01-14 18:39:15.589273000 +0700 @@ -109,6 +109,8 @@ enum tokens { TOK_UNREACH, TOK_CHECKSTATE, TOK_NAT, + TOK_NATIN, + TOK_NATOUT, TOK_REASS, TOK_CALL, TOK_RETURN, --- sbin/ipfw/ipfw2.c.orig 2020-01-03 15:01:57.529593000 +0700 +++ sbin/ipfw/ipfw2.c 2020-01-14 22:48:41.814835000 +0700 @@ -242,6 +242,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 */ }; @@ -279,6 +281,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 */ }; @@ -1806,6 +1810,7 @@ print_action_instruction(struct buf_pr * struct show_state *state, const ipfw_insn *cmd) { const char *s; + int tok; if (is_printed_opcode(state, cmd)) return (0); @@ -1940,6 +1945,17 @@ print_action_instruction(struct buf_pr * case O_EXTERNAL_DATA: if (state->eaction == NULL) break; + s = object_search_ctlv(fo->tstate, (cmd-1)->arg1, IPFW_TLV_EACTION); + if (s != NULL) { + tok = match_token(rule_eactions, s); + 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 @@ -4165,6 +4181,27 @@ chkarg: av++; 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--; --- sys/netpfil/ipfw/ip_fw_private.h.orig 2020-01-03 14:57:26.408354000 +0700 +++ sys/netpfil/ipfw/ip_fw_private.h 2020-01-14 18:59:15.166910000 +0700 @@ -123,6 +123,7 @@ struct ip_fw_args { struct mbuf *m; /* the mbuf chain */ struct ipfw_flow_id f_id; /* grabbed from IP header */ + uint32_t tablearg; }; MALLOC_DECLARE(M_IPFW); --- sys/netpfil/ipfw/ip_fw2.c.orig 2020-01-03 14:57:26.404301000 +0700 +++ sys/netpfil/ipfw/ip_fw2.c 2020-01-14 18:44:38.957822000 +0700 @@ -3131,6 +3131,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); /* @@ -3149,6 +3155,12 @@ do { \ */ DYN_INFO_INIT(&dyn_info); } + /* + * 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: --- sys/netpfil/ipfw/ip_fw_nat.c.orig 2020-01-03 14:57:26.407005000 +0700 +++ sys/netpfil/ipfw/ip_fw_nat.c 2020-01-14 18:58:19.742506000 +0700 @@ -55,6 +55,17 @@ __FBSDID("$FreeBSD: stable/11/sys/netpfi #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; @@ -287,7 +298,7 @@ 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; @@ -298,7 +309,7 @@ ipfw_nat(struct ip_fw_args *args, struct 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); @@ -345,7 +356,7 @@ ipfw_nat(struct ip_fw_args *args, struct /* 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); @@ -372,7 +383,8 @@ ipfw_nat(struct ip_fw_args *args, struct 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 @@ -388,8 +400,9 @@ ipfw_nat(struct ip_fw_args *args, struct * 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? */ @@ -454,6 +467,14 @@ ipfw_nat(struct ip_fw_args *args, struct 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) { @@ -1134,9 +1155,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); } @@ -1148,6 +1217,10 @@ vnet_ipfw_nat_uninit(const void *arg __u 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) {