diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -525,7 +525,7 @@ %token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS -%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO +%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO %token STRING %token NUMBER %token PORTBINARY @@ -962,6 +962,10 @@ } memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); + if (pf->astack[pf->asd + 1]) { if ($2 && strchr($2, '/') != NULL) { free($2); @@ -1094,6 +1098,9 @@ } memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = PF_NAT; r.af = $4; r.rtableid = $7; @@ -1115,6 +1122,9 @@ } memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = PF_RDR; r.af = $4; r.rtableid = $7; @@ -1157,6 +1167,10 @@ } memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); + r.action = PF_BINAT; r.af = $4; r.rtableid = $7; @@ -1422,6 +1436,9 @@ YYERROR; memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = $1.b1; r.direction = $2; @@ -1584,6 +1601,9 @@ for (i = $3; i; i = i->next) { bzero(&r, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = PF_DROP; r.direction = PF_IN; @@ -2369,6 +2389,9 @@ YYERROR; memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = $1.b1; switch ($1.b2) { @@ -3011,6 +3034,20 @@ filter_opts.marker |= FOM_SCRUB_TCP; filter_opts.marker |= $3.marker; } + | NATTO port_redirspec { + if (filter_opts.nat) { + yyerror("cannot respecify nat-to/binat-to"); + YYERROR; + } + filter_opts.nat = $2; + } + | RDRTO port_redirspec { + if (filter_opts.rdr) { + yyerror("cannot respecify rdr-to"); + YYERROR; + } + filter_opts.rdr = $2; + } | AFTO af FROM port_redirspec { if (filter_opts.nat) { yyerror("cannot respecify af-to"); @@ -4778,6 +4815,9 @@ YYERROR; memset(&r, 0, sizeof(r)); + TAILQ_INIT(&(r.rdr.list)); + TAILQ_INIT(&(r.nat.list)); + TAILQ_INIT(&(r.route.list)); r.action = $1.b1; r.natpass = $1.b2; @@ -4872,6 +4912,9 @@ YYERROR; memset(&binat, 0, sizeof(binat)); + TAILQ_INIT(&(binat.rdr.list)); + TAILQ_INIT(&(binat.nat.list)); + TAILQ_INIT(&(binat.route.list)); if ($1 && $3.b1) { yyerror("\"pass\" not valid with \"no\""); @@ -5022,8 +5065,6 @@ YYERROR; } - TAILQ_INIT(&binat.rdr.list); - TAILQ_INIT(&binat.nat.list); pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "binat: calloc"); @@ -5372,9 +5413,15 @@ problems++; } } - if (r->rdr.opts & PF_POOL_STICKYADDR && !r->keep_state) { - yyerror("'sticky-address' requires 'keep state'"); - problems++; + if (!TAILQ_EMPTY(&(r->nat.list)) || !TAILQ_EMPTY(&(r->rdr.list))) { + if (r->action != PF_MATCH && !r->keep_state) { + yyerror("nat-to and rdr-to require keep state"); + problems++; + } + if (r->direction == PF_INOUT) { + yyerror("nat-to and rdr-to require a direction"); + problems++; + } } return (-problems); } @@ -6118,7 +6165,6 @@ memcpy(&(rpool->key), rs->pool_opts.key, sizeof(struct pf_poolhashkey)); - TAILQ_INIT(&(rpool->list)); for (h = rs->host; h != NULL; h = h->next) { pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) @@ -6292,17 +6338,20 @@ if (r->action == PF_RDR) { error += apply_rdr_ports(r, &(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); } else if (r->action == PF_NAT) { error += apply_nat_ports(&(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); + } else { + error += apply_redirspec(&(r->route), route); + + error += apply_nat_ports(&(r->nat), nat); + error += apply_redirspec(&(r->nat), nat); + + error += apply_rdr_ports(r, &(r->rdr), rdr); + error += apply_redirspec(&(r->rdr), rdr); } - error += apply_redirspec(&(r->nat), nat); - error += apply_redirspec(&(r->rdr), rdr); - error += apply_redirspec(&(r->route), route); - - r->nat.proxy_port[0] = PF_NAT_PROXY_PORT_LOW; - r->nat.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH; - if (rule_consistent(r, anchor_call[0]) < 0 || error) yyerror("skipping rule due to errors"); else { @@ -6491,6 +6540,7 @@ { "modulate", MODULATE}, { "nat", NAT}, { "nat-anchor", NATANCHOR}, + { "nat-to", NATTO}, { "no", NO}, { "no-df", NODF}, { "no-route", NOROUTE}, @@ -6515,6 +6565,7 @@ { "random-id", RANDOMID}, { "rdr", RDR}, { "rdr-anchor", RDRANCHOR}, + { "rdr-to", RDRTO}, { "realtime", REALTIME}, { "reassemble", REASSEMBLE}, { "received-on", RECEIVEDON}, diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1240,25 +1240,34 @@ } #endif } - if (!anchor_call[0] && ! TAILQ_EMPTY(&r->nat.list) && - r->rule_flag & PFRULE_AFTO) { - printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6"); - print_pool(&r->nat, r->nat.proxy_port[0], r->nat.proxy_port[1], - r->naf ? r->naf : r->af, PF_NAT); + if (anchor_call[0]) + return; + if (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR) { + printf(" -> "); + print_pool(&r->rdr, r->rdr.proxy_port[0], + r->rdr.proxy_port[1], r->af, r->action); + } else { + if (!TAILQ_EMPTY(&r->nat.list)) { + if (r->rule_flag & PFRULE_AFTO) { + printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6"); + } else { + printf(" nat-to "); + } + print_pool(&r->nat, r->nat.proxy_port[0], + r->nat.proxy_port[1], r->naf ? r->naf : r->af, + PF_NAT); + } if (!TAILQ_EMPTY(&r->rdr.list)) { - printf(" to "); + if (r->rule_flag & PFRULE_AFTO) { + printf(" to "); + } else { + printf(" rdr-to "); + } print_pool(&r->rdr, r->rdr.proxy_port[0], r->rdr.proxy_port[1], r->naf ? r->naf : r->af, PF_RDR); } } - if (!anchor_call[0] && - (r->action == PF_NAT || r->action == PF_BINAT || - r->action == PF_RDR)) { - printf(" -> "); - print_pool(&r->rdr, r->rdr.proxy_port[0], - r->rdr.proxy_port[1], r->af, r->action); - } } void diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -2696,6 +2696,13 @@ int, struct pf_state_key **, struct pf_state_key **, struct pf_kanchor_stackframe *, struct pf_krule **, struct pf_udp_mapping **udp_mapping); +u_short pf_get_transaddr(struct pf_pdesc *, + struct pf_state_key **, struct pf_state_key **, + struct pf_krule *, struct pf_udp_mapping **, + u_int8_t, struct pf_kpool *); +int pf_translate_compat(struct pf_pdesc *, + struct pf_state_key *, struct pf_state_key *, + struct pf_krule *, u_int16_t); int pf_state_key_setup(struct pf_pdesc *, u_int16_t, u_int16_t, diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -347,7 +347,8 @@ struct pf_krule *, struct pf_pdesc *, struct pf_state_key *, struct pf_state_key *, int *, struct pf_kstate **, int, u_int16_t, u_int16_t, - struct pf_krule_slist *, struct pf_udp_mapping *); + struct pf_krule_slist *, struct pf_udp_mapping *, + struct pf_kpool *); static int pf_state_key_addr_setup(struct pf_pdesc *, struct pf_state_key_cmp *, int); static int pf_tcp_track_full(struct pf_kstate **, @@ -5466,6 +5467,58 @@ } \ } while (0) +static __inline u_short +pf_rule_apply_nat(struct pf_pdesc *pd, struct pf_state_key **skp, + struct pf_state_key **nkp, struct pf_krule *r, struct pf_krule **nr, + struct pf_udp_mapping **udp_mapping, u_int16_t virtual_type, int *rewrite, + struct pf_kpool **nat_pool) +{ + u_short transerror; + u_int8_t nat_action; + + if (r->rule_flag & PFRULE_AFTO) { + /* Don't translate if there was an old style NAT rule */ + if (*nr != NULL) + return (PFRES_TRANSLATE); + + /* pass af-to rules, unsupported on match rules */ + KASSERT(r->action != PF_MATCH, ("%s: af-to on match rule", __func__)); + /* XXX I can imagine scenarios where we have both NAT and RDR source tracking */ + *nat_pool = &(r->nat); + (*nr) = r; + pd->naf = r->naf; + if (pf_get_transaddr_af(*nr, pd) == -1) { + return (PFRES_TRANSLATE); + } + return (PFRES_MATCH); + } else if (r->rdr.cur || r->nat.cur) { + /* Don't translate if there was an old style NAT rule */ + if (*nr != NULL) + return (PFRES_TRANSLATE); + + /* match/pass nat-to/rdr-to rules */ + (*nr) = r; + if (r->nat.cur) { + nat_action = PF_NAT; + *nat_pool = &(r->nat); + } else { + nat_action = PF_RDR; + *nat_pool = &(r->rdr); + } + + transerror = pf_get_transaddr(pd, skp, nkp, *nr, udp_mapping, + nat_action, *nat_pool); + if (transerror == PFRES_MATCH) { + (*rewrite) += pf_translate_compat(pd, *skp, *nkp, *nr, + virtual_type); + return(PFRES_MATCH); + } + return (transerror); + } + + return (PFRES_MAX); +} + static int pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pf_pdesc *pd, struct pf_krule **am, @@ -5489,6 +5542,7 @@ u_int8_t icmptype = 0, icmpcode = 0; struct pf_kanchor_stackframe anchor_stack[PF_ANCHOR_STACKSIZE]; struct pf_udp_mapping *udp_mapping = NULL; + struct pf_kpool *nat_pool = NULL; PF_RULES_RASSERT(); @@ -5504,12 +5558,17 @@ pd->lookup.done = 1; } + if (pd->ip_sum) + bip_sum = *pd->ip_sum; + switch (pd->virtual_proto) { case IPPROTO_TCP: + bproto_sum = th->th_sum; pd->nsport = th->th_sport; pd->ndport = th->th_dport; break; case IPPROTO_UDP: + bproto_sum = pd->hdr.udp.uh_sum; pd->nsport = pd->hdr.udp.uh_sport; pd->ndport = pd->hdr.udp.uh_dport; break; @@ -5573,175 +5632,13 @@ case PFRES_MATCH: KASSERT(sk != NULL, ("%s: null sk", __func__)); KASSERT(nk != NULL, ("%s: null nk", __func__)); - if (nr->log) { PFLOG_PACKET(nr->action, PFRES_MATCH, nr, a, ruleset, pd, 1, NULL); } - if (pd->ip_sum) - bip_sum = *pd->ip_sum; - - switch (pd->proto) { - case IPPROTO_TCP: - bproto_sum = th->th_sum; - - if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || - nk->port[pd->sidx] != pd->nsport) { - pf_change_ap(pd->m, pd->src, &th->th_sport, - pd->ip_sum, &th->th_sum, &nk->addr[pd->sidx], - nk->port[pd->sidx], 0, pd->af, pd->naf); - pd->sport = &th->th_sport; - pd->nsport = th->th_sport; - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || - nk->port[pd->didx] != pd->ndport) { - pf_change_ap(pd->m, pd->dst, &th->th_dport, - pd->ip_sum, &th->th_sum, &nk->addr[pd->didx], - nk->port[pd->didx], 0, pd->af, pd->naf); - pd->dport = &th->th_dport; - pd->ndport = th->th_dport; - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - rewrite++; - break; - case IPPROTO_UDP: - bproto_sum = pd->hdr.udp.uh_sum; - - if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || - nk->port[pd->sidx] != pd->nsport) { - pf_change_ap(pd->m, pd->src, - &pd->hdr.udp.uh_sport, - pd->ip_sum, &pd->hdr.udp.uh_sum, - &nk->addr[pd->sidx], - nk->port[pd->sidx], 1, pd->af, pd->naf); - pd->sport = &pd->hdr.udp.uh_sport; - pd->nsport = pd->hdr.udp.uh_sport; - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || - nk->port[pd->didx] != pd->ndport) { - pf_change_ap(pd->m, pd->dst, - &pd->hdr.udp.uh_dport, - pd->ip_sum, &pd->hdr.udp.uh_sum, - &nk->addr[pd->didx], - nk->port[pd->didx], 1, pd->af, pd->naf); - pd->dport = &pd->hdr.udp.uh_dport; - pd->ndport = pd->hdr.udp.uh_dport; - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - rewrite++; - break; - case IPPROTO_SCTP: { - uint16_t checksum = 0; - - if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || - nk->port[pd->sidx] != pd->nsport) { - pf_change_ap(pd->m, pd->src, - &pd->hdr.sctp.src_port, pd->ip_sum, &checksum, - &nk->addr[pd->sidx], - nk->port[pd->sidx], 1, pd->af, pd->naf); - pd->sport = &pd->hdr.sctp.src_port; - pd->nsport = pd->hdr.sctp.src_port; - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || - nk->port[pd->didx] != pd->ndport) { - pf_change_ap(pd->m, pd->dst, - &pd->hdr.sctp.dest_port, pd->ip_sum, &checksum, - &nk->addr[pd->didx], - nk->port[pd->didx], 1, pd->af, pd->naf); - pd->dport = &pd->hdr.sctp.dest_port; - pd->ndport = pd->hdr.sctp.dest_port; - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - break; - } -#ifdef INET - case IPPROTO_ICMP: - if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET)) { - pf_change_a(&pd->src->v4.s_addr, pd->ip_sum, - nk->addr[pd->sidx].v4.s_addr, 0); - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET)) { - pf_change_a(&pd->dst->v4.s_addr, pd->ip_sum, - nk->addr[pd->didx].v4.s_addr, 0); - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - - if (virtual_type == htons(ICMP_ECHO) && - nk->port[pd->sidx] != pd->hdr.icmp.icmp_id) { - pd->hdr.icmp.icmp_cksum = pf_cksum_fixup( - pd->hdr.icmp.icmp_cksum, pd->nsport, - nk->port[pd->sidx], 0); - pd->hdr.icmp.icmp_id = nk->port[pd->sidx]; - pd->sport = &pd->hdr.icmp.icmp_id; - } - m_copyback(pd->m, pd->off, ICMP_MINLEN, (caddr_t)&pd->hdr.icmp); - break; -#endif /* INET */ -#ifdef INET6 - case IPPROTO_ICMPV6: - if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET6)) { - pf_change_a6(pd->src, &pd->hdr.icmp6.icmp6_cksum, - &nk->addr[pd->sidx], 0); - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET6)) { - pf_change_a6(pd->dst, &pd->hdr.icmp6.icmp6_cksum, - &nk->addr[pd->didx], 0); - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - rewrite++; - break; -#endif /* INET */ - default: - switch (pd->af) { -#ifdef INET - case AF_INET: - if (PF_ANEQ(&pd->nsaddr, - &nk->addr[pd->sidx], AF_INET)) { - pf_change_a(&pd->src->v4.s_addr, - pd->ip_sum, - nk->addr[pd->sidx].v4.s_addr, 0); - PF_ACPY(&pd->nsaddr, pd->src, pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, - &nk->addr[pd->didx], AF_INET)) { - pf_change_a(&pd->dst->v4.s_addr, - pd->ip_sum, - nk->addr[pd->didx].v4.s_addr, 0); - PF_ACPY(&pd->ndaddr, pd->dst, pd->af); - } - break; -#endif /* INET */ -#ifdef INET6 - case AF_INET6: - if (PF_ANEQ(&pd->nsaddr, - &nk->addr[pd->sidx], AF_INET6)) { - PF_ACPY(&pd->nsaddr, &nk->addr[pd->sidx], pd->af); - PF_ACPY(pd->src, &nk->addr[pd->sidx], pd->af); - } - - if (PF_ANEQ(&pd->ndaddr, - &nk->addr[pd->didx], AF_INET6)) { - PF_ACPY(&pd->ndaddr, &nk->addr[pd->didx], pd->af); - PF_ACPY(pd->dst, &nk->addr[pd->didx], pd->af); - } - break; -#endif /* INET */ - } - break; - } - if (nr->natpass) - r = NULL; + rewrite += pf_translate_compat(pd, sk, nk, nr, virtual_type); + nat_pool = &(nr->rdr); } while (r != NULL) { @@ -5847,6 +5744,24 @@ tag = r->tag; if (r->anchor == NULL) { if (r->action == PF_MATCH) { + /* + * Apply translations before increasing counters, + * in case it fails. + */ + transerror = pf_rule_apply_nat(pd, &sk, &nk, r, + &nr, &udp_mapping, virtual_type, &rewrite, + &nat_pool); + switch (transerror) { + case PFRES_MATCH: + /* Translation action found in rule and applied successfully */ + case PFRES_MAX: + /* No translation action found in rule */ + break; + default: + /* Translation action found in rule but failed to apply */ + REASON_SET(&reason, transerror); + goto cleanup; + } ri = malloc(sizeof(struct pf_krule_item), M_PF_RULE_ITEM, M_NOWAIT | M_ZERO); if (ri == NULL) { REASON_SET(&reason, PFRES_MEMORY); @@ -5859,14 +5774,6 @@ pf_counter_u64_add_protected(&r->bytes[pd->dir == PF_OUT], pd->tot_len); pf_counter_u64_critical_exit(); pf_rule_to_actions(r, &pd->act); - if (r->rule_flag & PFRULE_AFTO) - pd->naf = r->naf; - if (pd->af != pd->naf) { - if (pf_get_transaddr_af(r, pd) == -1) { - REASON_SET(&reason, PFRES_TRANSLATE); - goto cleanup; - } - } if (r->log) PFLOG_PACKET(r->action, PFRES_MATCH, r, a, ruleset, pd, 1, NULL); @@ -5897,13 +5804,18 @@ /* apply actions for last matching pass/block rule */ pf_rule_to_actions(r, &pd->act); - if (r->rule_flag & PFRULE_AFTO) - pd->naf = r->naf; - if (pd->af != pd->naf) { - if (pf_get_transaddr_af(r, pd) == -1) { - REASON_SET(&reason, PFRES_TRANSLATE); - goto cleanup; - } + transerror = pf_rule_apply_nat(pd, &sk, &nk, r, &nr, &udp_mapping, + virtual_type, &rewrite, &nat_pool); + switch (transerror) { + case PFRES_MATCH: + /* Translation action found in rule and applied successfully */ + case PFRES_MAX: + /* No translation action found in rule */ + break; + default: + /* Translation action found in rule but failed to apply */ + REASON_SET(&reason, transerror); + goto cleanup; } if (r->log) { @@ -5961,7 +5873,7 @@ action = pf_create_state(r, nr, a, pd, nk, sk, &rewrite, sm, tag, bproto_sum, bip_sum, - &match_rules, udp_mapping); + &match_rules, udp_mapping, nat_pool); if (action != PF_PASS) { pf_udp_mapping_release(udp_mapping); pd->act.log |= PF_LOG_FORCE; @@ -6048,7 +5960,7 @@ struct pf_pdesc *pd, struct pf_state_key *nk, struct pf_state_key *sk, int *rewrite, struct pf_kstate **sm, int tag, u_int16_t bproto_sum, u_int16_t bip_sum, struct pf_krule_slist *match_rules, - struct pf_udp_mapping *udp_mapping) + struct pf_udp_mapping *udp_mapping, struct pf_kpool *nat_pool) { struct pf_kstate *s = NULL; struct pf_ksrc_node *sns[PF_SN_MAX] = { NULL }; @@ -6079,20 +5991,32 @@ goto csfailed; } /* src node for route-to rule */ - if (TAILQ_EMPTY(&pool_route->list)) /* Backwards compatibility. */ - pool_route = &r->rdr; - if ((pool_route->opts & PF_POOL_STICKYADDR) && - (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, pd->af, - &pd->act.rt_addr, pd->act.rt_kif, PF_SN_ROUTE)) != 0) { - REASON_SET(&reason, sn_reason); - goto csfailed; + + if (r->rt) { + /* + * Backwards compatibility. + * XXX: Should we move it to rule creation? + */ + if (TAILQ_EMPTY(&pool_route->list)) + pool_route = &r->rdr; + if ((pool_route->opts & PF_POOL_STICKYADDR) && + (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, + pd->af, &pd->act.rt_addr, pd->act.rt_kif, + PF_SN_ROUTE)) != 0) { + REASON_SET(&reason, sn_reason); + goto csfailed; + } } /* src node for translation rule */ - if (nr != NULL && (nr->rdr.opts & PF_POOL_STICKYADDR) && - (sn_reason = pf_insert_src_node(sns, snhs, nr, &sk->addr[pd->sidx], - pd->af, &nk->addr[1], NULL, PF_SN_NAT)) != 0 ) { - REASON_SET(&reason, sn_reason); - goto csfailed; + if (nr != NULL) { + KASSERT(nat_pool != NULL, ("%s: nat_pool is NULL", __func__)); + if ((nat_pool->opts & PF_POOL_STICKYADDR) && + (sn_reason = pf_insert_src_node(sns, snhs, nr, + &sk->addr[pd->sidx], pd->af, &nk->addr[1], NULL, + PF_SN_NAT)) != 0 ) { + REASON_SET(&reason, sn_reason); + goto csfailed; + } } s = pf_alloc_state(M_NOWAIT); if (s == NULL) { @@ -6208,9 +6132,7 @@ /* * sk/nk could already been setup by pf_get_translation(). */ - if (nr == NULL) { - KASSERT((sk == NULL && nk == NULL), ("%s: nr %p sk %p, nk %p", - __func__, nr, sk, nk)); + if (sk == NULL && nk == NULL) { MPASS(pd->sport == NULL || (pd->osport == *pd->sport)); MPASS(pd->dport == NULL || (pd->odport == *pd->dport)); if (pf_state_key_setup(pd, pd->nsport, pd->ndport, &sk, &nk)) { @@ -6423,6 +6345,174 @@ return (rewrite); } +int +pf_translate_compat(struct pf_pdesc *pd, struct pf_state_key *sk, + struct pf_state_key *nk, struct pf_krule *nr, u_int16_t virtual_type) +{ + struct tcphdr *th = &pd->hdr.tcp; + int rewrite = 0; + + KASSERT(sk != NULL, ("%s: null sk", __func__)); + KASSERT(nk != NULL, ("%s: null nk", __func__)); + + switch (pd->proto) { + case IPPROTO_TCP: + if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || + nk->port[pd->sidx] != pd->nsport) { + pf_change_ap(pd->m, pd->src, &th->th_sport, + pd->ip_sum, &th->th_sum, &nk->addr[pd->sidx], + nk->port[pd->sidx], 0, pd->af, pd->naf); + pd->sport = &th->th_sport; + pd->nsport = th->th_sport; + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || + nk->port[pd->didx] != pd->ndport) { + pf_change_ap(pd->m, pd->dst, &th->th_dport, + pd->ip_sum, &th->th_sum, &nk->addr[pd->didx], + nk->port[pd->didx], 0, pd->af, pd->naf); + pd->dport = &th->th_dport; + pd->ndport = th->th_dport; + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + rewrite++; + break; + case IPPROTO_UDP: + + if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || + nk->port[pd->sidx] != pd->nsport) { + pf_change_ap(pd->m, pd->src, + &pd->hdr.udp.uh_sport, + pd->ip_sum, &pd->hdr.udp.uh_sum, + &nk->addr[pd->sidx], + nk->port[pd->sidx], 1, pd->af, pd->naf); + pd->sport = &pd->hdr.udp.uh_sport; + pd->nsport = pd->hdr.udp.uh_sport; + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || + nk->port[pd->didx] != pd->ndport) { + pf_change_ap(pd->m, pd->dst, + &pd->hdr.udp.uh_dport, + pd->ip_sum, &pd->hdr.udp.uh_sum, + &nk->addr[pd->didx], + nk->port[pd->didx], 1, pd->af, pd->naf); + pd->dport = &pd->hdr.udp.uh_dport; + pd->ndport = pd->hdr.udp.uh_dport; + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + rewrite++; + break; + case IPPROTO_SCTP: { + uint16_t checksum = 0; + + if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || + nk->port[pd->sidx] != pd->nsport) { + pf_change_ap(pd->m, pd->src, + &pd->hdr.sctp.src_port, pd->ip_sum, &checksum, + &nk->addr[pd->sidx], + nk->port[pd->sidx], 1, pd->af, pd->naf); + pd->sport = &pd->hdr.sctp.src_port; + pd->nsport = pd->hdr.sctp.src_port; + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) || + nk->port[pd->didx] != pd->ndport) { + pf_change_ap(pd->m, pd->dst, + &pd->hdr.sctp.dest_port, pd->ip_sum, &checksum, + &nk->addr[pd->didx], + nk->port[pd->didx], 1, pd->af, pd->naf); + pd->dport = &pd->hdr.sctp.dest_port; + pd->ndport = pd->hdr.sctp.dest_port; + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + break; + } +#ifdef INET + case IPPROTO_ICMP: + if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET)) { + pf_change_a(&pd->src->v4.s_addr, pd->ip_sum, + nk->addr[pd->sidx].v4.s_addr, 0); + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET)) { + pf_change_a(&pd->dst->v4.s_addr, pd->ip_sum, + nk->addr[pd->didx].v4.s_addr, 0); + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + + if (virtual_type == htons(ICMP_ECHO) && + nk->port[pd->sidx] != pd->hdr.icmp.icmp_id) { + pd->hdr.icmp.icmp_cksum = pf_cksum_fixup( + pd->hdr.icmp.icmp_cksum, pd->nsport, + nk->port[pd->sidx], 0); + pd->hdr.icmp.icmp_id = nk->port[pd->sidx]; + pd->sport = &pd->hdr.icmp.icmp_id; + } + m_copyback(pd->m, pd->off, ICMP_MINLEN, (caddr_t)&pd->hdr.icmp); + break; +#endif /* INET */ +#ifdef INET6 + case IPPROTO_ICMPV6: + if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET6)) { + pf_change_a6(pd->src, &pd->hdr.icmp6.icmp6_cksum, + &nk->addr[pd->sidx], 0); + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET6)) { + pf_change_a6(pd->dst, &pd->hdr.icmp6.icmp6_cksum, + &nk->addr[pd->didx], 0); + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + rewrite++; + break; +#endif /* INET */ + default: + switch (pd->af) { +#ifdef INET + case AF_INET: + if (PF_ANEQ(&pd->nsaddr, + &nk->addr[pd->sidx], AF_INET)) { + pf_change_a(&pd->src->v4.s_addr, + pd->ip_sum, + nk->addr[pd->sidx].v4.s_addr, 0); + PF_ACPY(&pd->nsaddr, pd->src, pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, + &nk->addr[pd->didx], AF_INET)) { + pf_change_a(&pd->dst->v4.s_addr, + pd->ip_sum, + nk->addr[pd->didx].v4.s_addr, 0); + PF_ACPY(&pd->ndaddr, pd->dst, pd->af); + } + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + if (PF_ANEQ(&pd->nsaddr, + &nk->addr[pd->sidx], AF_INET6)) { + PF_ACPY(&pd->nsaddr, &nk->addr[pd->sidx], pd->af); + PF_ACPY(pd->src, &nk->addr[pd->sidx], pd->af); + } + + if (PF_ANEQ(&pd->ndaddr, + &nk->addr[pd->didx], AF_INET6)) { + PF_ACPY(&pd->ndaddr, &nk->addr[pd->didx], pd->af); + PF_ACPY(pd->dst, &nk->addr[pd->didx], pd->af); + } + break; +#endif /* INET */ + } + break; + } + return (rewrite); +} + static int pf_tcp_track_full(struct pf_kstate **state, struct pf_pdesc *pd, u_short *reason, int *copyback, struct pf_state_peer *src, diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c --- a/sys/netpfil/pf/pf_lb.c +++ b/sys/netpfil/pf/pf_lb.c @@ -75,7 +75,7 @@ static uint64_t pf_hash(struct pf_addr *, struct pf_addr *, struct pf_poolhashkey *, sa_family_t); -static struct pf_krule *pf_match_translation(struct pf_pdesc *, +struct pf_krule *pf_match_translation(struct pf_pdesc *, int, struct pf_kanchor_stackframe *); static int pf_get_sport(struct pf_pdesc *, struct pf_krule *, struct pf_addr *, uint16_t *, uint16_t, uint16_t, @@ -126,7 +126,7 @@ return (res); } -static struct pf_krule * +struct pf_krule * pf_match_translation(struct pf_pdesc *pd, int rs_num, struct pf_kanchor_stackframe *anchor_stack) { @@ -422,19 +422,19 @@ pf_get_mape_sport(struct pf_pdesc *pd, struct pf_krule *r, struct pf_addr *naddr, uint16_t *nport, struct pf_ksrc_node **sn, struct pf_srchash **sh, - struct pf_udp_mapping **udp_mapping) + struct pf_udp_mapping **udp_mapping, struct pf_kpool *rpool) { uint16_t psmask, low, highmask; uint16_t i, ahigh, cut; int ashift, psidshift; - ashift = 16 - r->rdr.mape.offset; - psidshift = ashift - r->rdr.mape.psidlen; - psmask = r->rdr.mape.psid & ((1U << r->rdr.mape.psidlen) - 1); + ashift = 16 - rpool->mape.offset; + psidshift = ashift - rpool->mape.psidlen; + psmask = rpool->mape.psid & ((1U << rpool->mape.psidlen) - 1); psmask = psmask << psidshift; highmask = (1U << psidshift) - 1; - ahigh = (1U << r->rdr.mape.offset) - 1; + ahigh = (1U << rpool->mape.offset) - 1; cut = arc4random() & ahigh; if (cut == 0) cut = 1; @@ -442,14 +442,14 @@ for (i = cut; i <= ahigh; i++) { low = (i << ashift) | psmask; if (!pf_get_sport(pd, r, - naddr, nport, low, low | highmask, sn, sh, &r->rdr, + naddr, nport, low, low | highmask, sn, sh, rpool, udp_mapping, PF_SN_NAT)) return (0); } for (i = cut - 1; i > 0; i--) { low = (i << ashift) | psmask; if (!pf_get_sport(pd, r, - naddr, nport, low, low | highmask, sn, sh, &r->rdr, + naddr, nport, low, low | highmask, sn, sh, rpool, udp_mapping, PF_SN_NAT)) return (0); } @@ -768,12 +768,7 @@ struct pf_udp_mapping **udp_mapping) { struct pf_krule *r = NULL; - struct pf_addr *naddr; - struct pf_ksrc_node *sn = NULL; - struct pf_srchash *sh = NULL; - uint16_t *nportp; - uint16_t low, high; - u_short reason; + u_short transerror; PF_RULES_RASSERT(); KASSERT(*skp == NULL, ("*skp not NULL")); @@ -801,38 +796,64 @@ return (PFRES_MAX); } - if (pf_state_key_setup(pd, pd->nsport, pd->ndport, skp, nkp)) - return (PFRES_MEMORY); + transerror = pf_get_transaddr(pd, skp, nkp, r, udp_mapping, r->action, &(r->rdr)); + if (transerror == PFRES_MATCH) + *rp = r; + + return (transerror); +} + +u_short +pf_get_transaddr(struct pf_pdesc *pd, struct pf_state_key **skp, + struct pf_state_key **nkp, struct pf_krule *r, + struct pf_udp_mapping **udp_mapping, u_int8_t nat_action, + struct pf_kpool *rpool) +{ + struct pf_addr *naddr; + struct pf_ksrc_node *sn = NULL; + struct pf_srchash *sh = NULL; + uint16_t *nportp; + uint16_t low, high; + u_short reason; + + PF_RULES_RASSERT(); + KASSERT(r != NULL, ("r is NULL")); + KASSERT(!(r->rule_flag & PFRULE_AFTO), ("AFTO rule")); + + if (*skp == NULL && *nkp == NULL) { + if (pf_state_key_setup(pd, pd->nsport, pd->ndport, skp, nkp)) + return (PFRES_MEMORY); + } naddr = &(*nkp)->addr[1]; nportp = &(*nkp)->port[1]; - switch (r->action) { + switch (nat_action) { case PF_NAT: if (pd->proto == IPPROTO_ICMP) { low = 1; high = 65535; } else { - low = r->rdr.proxy_port[0]; - high = r->rdr.proxy_port[1]; + low = rpool->proxy_port[0]; + high = rpool->proxy_port[1]; } - if (r->rdr.mape.offset > 0) { + if (rpool->mape.offset > 0) { if (pf_get_mape_sport(pd, r, naddr, nportp, &sn, - &sh, udp_mapping)) { + &sh, udp_mapping, rpool)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: MAP-E port allocation (%u/%u/%u)" " failed\n", - r->rdr.mape.offset, - r->rdr.mape.psidlen, - r->rdr.mape.psid)); + rpool->mape.offset, + rpool->mape.psidlen, + rpool->mape.psid)); reason = PFRES_MAPFAILED; goto notrans; } } else if (pf_get_sport(pd, r, naddr, nportp, low, high, &sn, - &sh, &r->rdr, udp_mapping, PF_SN_NAT)) { + &sh, rpool, udp_mapping, PF_SN_NAT)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: NAT proxy port allocation (%u-%u) failed\n", - r->rdr.proxy_port[0], r->rdr.proxy_port[1])); + rpool->proxy_port[0], rpool->proxy_port[1])); reason = PFRES_MAPFAILED; goto notrans; } @@ -840,41 +861,39 @@ case PF_BINAT: switch (pd->dir) { case PF_OUT: - if (r->rdr.cur->addr.type == PF_ADDR_DYNIFTL){ + if (rpool->cur->addr.type == PF_ADDR_DYNIFTL){ switch (pd->af) { #ifdef INET case AF_INET: - if (r->rdr.cur->addr.p.dyn-> + if (rpool->cur->addr.p.dyn-> pfid_acnt4 < 1) { reason = PFRES_MAPFAILED; goto notrans; } PF_POOLMASK(naddr, - &r->rdr.cur->addr.p.dyn-> - pfid_addr4, - &r->rdr.cur->addr.p.dyn-> - pfid_mask4, &pd->nsaddr, AF_INET); + &rpool->cur->addr.p.dyn->pfid_addr4, + &rpool->cur->addr.p.dyn->pfid_mask4, + &pd->nsaddr, AF_INET); break; #endif /* INET */ #ifdef INET6 case AF_INET6: - if (r->rdr.cur->addr.p.dyn-> + if (rpool->cur->addr.p.dyn-> pfid_acnt6 < 1) { reason = PFRES_MAPFAILED; goto notrans; } PF_POOLMASK(naddr, - &r->rdr.cur->addr.p.dyn-> - pfid_addr6, - &r->rdr.cur->addr.p.dyn-> - pfid_mask6, &pd->nsaddr, AF_INET6); + &rpool->cur->addr.p.dyn->pfid_addr6, + &rpool->cur->addr.p.dyn->pfid_mask6, + &pd->nsaddr, AF_INET6); break; #endif /* INET6 */ } } else PF_POOLMASK(naddr, - &r->rdr.cur->addr.v.a.addr, - &r->rdr.cur->addr.v.a.mask, &pd->nsaddr, + &rpool->cur->addr.v.a.addr, + &rpool->cur->addr.v.a.mask, &pd->nsaddr, pd->af); break; case PF_IN: @@ -917,30 +936,30 @@ uint16_t cut, low, high, nport; reason = pf_map_addr_sn(pd->af, r, &pd->nsaddr, naddr, NULL, - NULL, &sn, &sh, &r->rdr, PF_SN_NAT); + NULL, &sn, &sh, rpool, PF_SN_NAT); if (reason != 0) goto notrans; - if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK) - PF_POOLMASK(naddr, naddr, &r->rdr.cur->addr.v.a.mask, + if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK) + PF_POOLMASK(naddr, naddr, &rpool->cur->addr.v.a.mask, &pd->ndaddr, pd->af); /* Do not change SCTP ports. */ if (pd->proto == IPPROTO_SCTP) break; - if (r->rdr.proxy_port[1]) { + if (rpool->proxy_port[1]) { uint32_t tmp_nport; tmp_nport = ((ntohs(pd->ndport) - ntohs(r->dst.port[0])) % - (r->rdr.proxy_port[1] - r->rdr.proxy_port[0] + - 1)) + r->rdr.proxy_port[0]; + (rpool->proxy_port[1] - rpool->proxy_port[0] + + 1)) + rpool->proxy_port[0]; /* Wrap around if necessary. */ if (tmp_nport > 65535) tmp_nport -= 65535; nport = htons((uint16_t)tmp_nport); - } else if (r->rdr.proxy_port[0]) - nport = htons(r->rdr.proxy_port[0]); + } else if (rpool->proxy_port[0]) + nport = htons(rpool->proxy_port[0]); else nport = pd->ndport; @@ -1015,7 +1034,6 @@ /* Return success only if translation really happened. */ if (bcmp(*skp, *nkp, sizeof(struct pf_state_key_cmp))) { - *rp = r; return (PFRES_MATCH); } @@ -1028,6 +1046,7 @@ return (reason); } + int pf_get_transaddr_af(struct pf_krule *r, struct pf_pdesc *pd) { diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -21,7 +21,6 @@ loginterface \ killstate \ macro \ - map_e \ match \ max_states \ mbuf \ diff --git a/tests/sys/netpfil/pf/map_e.sh b/tests/sys/netpfil/pf/map_e.sh deleted file mode 100644 --- a/tests/sys/netpfil/pf/map_e.sh +++ /dev/null @@ -1,90 +0,0 @@ -# -# SPDX-License-Identifier: BSD-2-Clause -# -# Copyright (c) 2021 KUROSAWA Takahiro -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. - -. $(atf_get_srcdir)/utils.subr - -atf_test_case "map_e" "cleanup" -map_e_head() -{ - atf_set descr 'map-e-portset test' - atf_set require.user root -} - -map_e_body() -{ - NC_TRY_COUNT=12 - - pft_init - - epair_map_e=$(vnet_mkepair) - epair_echo=$(vnet_mkepair) - - vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a - vnet_mkjail echo ${epair_echo}b - - ifconfig ${epair_map_e}a 192.0.2.2/24 up - route add -net 198.51.100.0/24 192.0.2.1 - - jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up - jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up - jexec map_e sysctl net.inet.ip.forwarding=1 - - jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up - jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf - - # Enable pf! - jexec map_e pfctl -e - pft_set_rules map_e \ - "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342" - - # Only allow specified ports. - jexec echo pfctl -e - pft_set_rules echo "block return all" \ - "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \ - "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \ - "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \ - "set skip on lo" - - i=0 - while [ ${i} -lt ${NC_TRY_COUNT} ] - do - echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7 - if [ $? -ne 0 ]; then - atf_fail "nc failed (${i})" - fi - i=$((${i}+1)) - done -} - -map_e_cleanup() -{ - pft_cleanup -} - -atf_init_test_cases() -{ - atf_add_test_case "map_e" -} diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh --- a/tests/sys/netpfil/pf/nat.sh +++ b/tests/sys/netpfil/pf/nat.sh @@ -2,6 +2,8 @@ # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2018 Kristof Provost +# Copyright (c) 2025 Kajetan Staszkiewicz +# Copyright (c) 2021 KUROSAWA Takahiro # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -112,14 +114,7 @@ } -atf_test_case "endpoint_independent" "cleanup" -endpoint_independent_head() -{ - atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers' - atf_set require.user root -} - -endpoint_independent_body() +endpoint_independent_setup() { pft_init filter="udp and dst port 1234" # only capture udp pings @@ -153,13 +148,15 @@ jexec server1 ifconfig ${epair_server1}a 198.51.100.32/24 up jexec server2 ifconfig ${epair_server2}a 198.51.100.22/24 up +} +endpoint_independent_common() +{ # Enable pf! jexec nat pfctl -e # validate non-endpoint independent nat rule behaviour - pft_set_rules nat \ - "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" + pft_set_rules nat "${1}" jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \ --immediate-mode $filter & @@ -198,8 +195,7 @@ fi # validate endpoint independent nat rule behaviour - pft_set_rules nat \ - "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent" + pft_set_rules nat "${2}" jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \ --immediate-mode $filter & @@ -238,7 +234,47 @@ fi } -endpoint_independent_cleanup() +atf_test_case "endpoint_independent_compat" "cleanup" +endpoint_independent_compat_head() +{ + atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers' + atf_set require.user root +} + +endpoint_independent_compat_body() +{ + endpoint_independent_setup # Sets ${epair_…} variables + + endpoint_independent_common \ + "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" \ + "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent" +} + +endpoint_independent_compat_cleanup() +{ + pft_cleanup + rm -f server1.out + rm -f server2.out +} + +atf_test_case "endpoint_independent_pass" "cleanup" +endpoint_independent_pass_head() +{ + atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers' + atf_set require.user root +} + +endpoint_independent_pass_body() +{ + endpoint_independent_setup # Sets ${epair_…} variables + + endpoint_independent_common \ + "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) keep state" \ + "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) endpoint-independent keep state" + +} + +endpoint_independent_pass_cleanup() { pft_cleanup rm -f server1.out @@ -438,14 +474,189 @@ pft_cleanup } +nat_pass_head() +{ + atf_set descr 'IPv4 NAT on pass rule' + atf_set require.user root + atf_set require.progs scapy +} + +nat_pass_body() +{ + setup_router_server_ipv4 + # Delete the route back to make sure that the traffic has been NAT-ed + jexec server route del -net ${net_tester} ${net_server_host_router} + + pft_set_rules router \ + "block" \ + "pass in on ${epair_tester}b inet proto tcp keep state" \ + "pass out on ${epair_server}a inet proto tcp nat-to ${epair_server}a keep state" + + ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201 + + jexec router pfctl -qvvsr + jexec router pfctl -qvvss + jexec router ifconfig + jexec router netstat -rn +} + +nat_pass_cleanup() +{ + pft_cleanup +} + +nat_match_head() +{ + atf_set descr 'IPv4 NAT on match rule' + atf_set require.user root + atf_set require.progs scapy +} + +nat_match_body() +{ + setup_router_server_ipv4 + # Delete the route back to make sure that the traffic has been NAT-ed + jexec server route del -net ${net_tester} ${net_server_host_router} + + # NAT is applied during ruleset evaluation: + # rules after "match" match on NAT-ed address + pft_set_rules router \ + "block" \ + "pass in on ${epair_tester}b inet proto tcp keep state" \ + "match out on ${epair_server}a inet proto tcp nat-to ${epair_server}a" \ + "pass out on ${epair_server}a inet proto tcp from ${epair_server}a keep state" + + ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201 + + jexec router pfctl -qvvsr + jexec router pfctl -qvvss + jexec router ifconfig + jexec router netstat -rn +} + +nat_match_cleanup() +{ + pft_cleanup +} + +map_e_common() +{ + NC_TRY_COUNT=12 + + pft_init + + epair_map_e=$(vnet_mkepair) + epair_echo=$(vnet_mkepair) + + vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a + vnet_mkjail echo ${epair_echo}b + + ifconfig ${epair_map_e}a 192.0.2.2/24 up + route add -net 198.51.100.0/24 192.0.2.1 + + jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up + jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up + jexec map_e sysctl net.inet.ip.forwarding=1 + + jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up + jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf + + # Enable pf! + jexec map_e pfctl -e +} + +atf_test_case "map_e_compat" "cleanup" +map_e_compat_head() +{ + atf_set descr 'map-e-portset test' + atf_set require.user root +} + +map_e_compat_body() +{ + map_e_common + + pft_set_rules map_e \ + "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342" + + # Only allow specified ports. + jexec echo pfctl -e + pft_set_rules echo "block return all" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \ + "set skip on lo" + + i=0 + while [ ${i} -lt ${NC_TRY_COUNT} ] + do + echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7 + if [ $? -ne 0 ]; then + atf_fail "nc failed (${i})" + fi + i=$((${i}+1)) + done +} + +map_e_compat_cleanup() +{ + pft_cleanup +} + + +atf_test_case "map_e_pass" "cleanup" +map_e_pass_head() +{ + atf_set descr 'map-e-portset test' + atf_set require.user root +} + +map_e_pass_body() +{ + map_e_common + + pft_set_rules map_e \ + "pass out on ${epair_echo}a inet from 192.0.2.0/24 to any nat-to (${epair_echo}a) map-e-portset 2/12/0x342 keep state" + + jexec map_e pfctl -qvvsr + + # Only allow specified ports. + jexec echo pfctl -e + pft_set_rules echo "block return all" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \ + "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \ + "set skip on lo" + + i=0 + while [ ${i} -lt ${NC_TRY_COUNT} ] + do + echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7 + if [ $? -ne 0 ]; then + atf_fail "nc failed (${i})" + fi + i=$((${i}+1)) + done +} + +map_e_pass_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "exhaust" atf_add_test_case "nested_anchor" - atf_add_test_case "endpoint_independent" + atf_add_test_case "endpoint_independent_compat" + atf_add_test_case "endpoint_independent_pass" atf_add_test_case "nat6_nolinklocal" atf_add_test_case "empty_table_source_hash" atf_add_test_case "no_addrs_source_hash" atf_add_test_case "empty_table_random" atf_add_test_case "no_addrs_random" + atf_add_test_case "map_e_compat" + atf_add_test_case "map_e_pass" + atf_add_test_case "nat_pass" + atf_add_test_case "nat_match" } diff --git a/tests/sys/netpfil/pf/rdr.sh b/tests/sys/netpfil/pf/rdr.sh --- a/tests/sys/netpfil/pf/rdr.sh +++ b/tests/sys/netpfil/pf/rdr.sh @@ -27,14 +27,6 @@ . $(atf_get_srcdir)/utils.subr -atf_test_case "tcp_v6" "cleanup" -tcp_v6_head() -{ - atf_set descr 'TCP rdr with IPv6' - atf_set require.user root - atf_set require.progs python3 -} - # # Test that rdr works for TCP with IPv6. # @@ -47,7 +39,7 @@ # # Test for incorrect checksums after the rewrite by looking at a packet capture (see bug 210860) # -tcp_v6_body() +tcp_v6_setup() { pft_init @@ -83,9 +75,11 @@ jexec ${j}c route add -inet6 2001:db8:a::0/64 2001:db8:b::1 jexec ${j}b pfctl -e +} - pft_set_rules ${j}b \ - "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000" +tcp_v6_common() +{ + pft_set_rules ${j}b "${1}" # Check that a can reach c over the router atf_check -s exit:0 -o ignore \ @@ -116,19 +110,44 @@ atf_check_equal " 0" "$count" } -tcp_v6_cleanup() +atf_test_case "tcp_v6_compat" "cleanup" +tcp_v6_compat_head() +{ + atf_set descr 'TCP rdr with IPv6 with NAT rules' + atf_set require.user root + atf_set require.progs python3 +} + +tcp_v6_compat_body() +{ + tcp_v6_setup # Sets ${epair_…} variables + tcp_v6_common \ + "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000" +} + +tcp_v6_compat_cleanup() { pft_cleanup } - -atf_test_case "srcport" "cleanup" -srcport_head() +atf_test_case "tcp_v6_pass" "cleanup" +tcp_v6_pass_head() { - atf_set descr 'TCP rdr srcport modulation' + atf_set descr 'TCP rdr with IPv6 with pass/match rules' atf_set require.user root atf_set require.progs python3 - atf_set timeout 9999 +} + +tcp_v6_pass_body() +{ + tcp_v6_setup # Sets ${epair_…} variables + tcp_v6_common \ + "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000" +} + +tcp_v6_pass_cleanup() +{ + pft_cleanup } # @@ -145,7 +164,7 @@ # In this case, the rdr rule should also rewrite the source port (again) to # resolve the state conflict. # -srcport_body() +srcport_setup() { pft_init @@ -188,14 +207,17 @@ jexec ${j}c sysctl net.inet.ip.forwarding=1 jexec ${j}b pfctl -e jexec ${j}c pfctl -e +} +srcport_common() +{ pft_set_rules ${j}b \ "set debug misc" \ - "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port" + "${1}" pft_set_rules ${j}c \ "set debug misc" \ - "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888" + "${2}" jexec ${j}a route add default 198.51.100.1 jexec ${j}c route add 198.51.100.0/24 198.51.101.2 @@ -215,13 +237,54 @@ atf_check -o match:"[0-9]+" -o not-inline:"1234" cat port3 } -srcport_cleanup() +atf_test_case "srcport_compat" "cleanup" +srcport_compat_head() +{ + atf_set descr 'TCP rdr srcport modulation with NAT rules' + atf_set require.user root + atf_set require.progs python3 + atf_set timeout 9999 +} + +srcport_compat_body() +{ + srcport_setup # Sets ${epair_…} variables + srcport_common \ + "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port" \ + "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888" +} + +srcport_compat_cleanup() +{ + pft_cleanup +} + +atf_test_case "srcport_pass" "cleanup" +srcport_pass_head() +{ + atf_set descr 'TCP rdr srcport modulation with pass/match rules' + atf_set require.user root + atf_set require.progs python3 + atf_set timeout 9999 +} + +srcport_pass_body() +{ + srcport_setup # Sets ${epair_…} variables + srcport_common \ + "pass out on ${epair2}a inet from 198.51.100.0/24 to any nat-to ${epair2}a static-port" \ + "pass in on ${epair2}b proto tcp from any to ${epair2}b port 7777 rdr-to 203.0.113.50 port 8888" +} + +srcport_pass_cleanup() { pft_cleanup } atf_init_test_cases() { - atf_add_test_case "tcp_v6" - atf_add_test_case "srcport" + atf_add_test_case "tcp_v6_compat" + atf_add_test_case "tcp_v6_pass" + atf_add_test_case "srcport_compat" + atf_add_test_case "srcport_pass" } diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh --- a/tests/sys/netpfil/pf/src_track.sh +++ b/tests/sys/netpfil/pf/src_track.sh @@ -307,14 +307,14 @@ pft_cleanup } -route_to_head() +sn_types_compat_head() { - atf_set descr 'Max states per source per rule with route-to' + atf_set descr 'Combination of source node types with compat NAT rules' atf_set require.user root atf_set require.progs python3 scapy } -route_to_body() +sn_types_compat_body() { setup_router_dummy_ipv6 @@ -398,11 +398,110 @@ ! grep -q 'filter rule 3' $nodes || atf_fail "Source node found for rule 3" } -route_to_cleanup() +sn_types_compat_cleanup() { pft_cleanup } +sn_types_pass_head() +{ + atf_set descr 'Combination of source node types with pass NAT rules' + atf_set require.user root + atf_set require.progs python3 scapy +} + +sn_types_pass_body() +{ + setup_router_dummy_ipv6 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 2001:db8:44::0/64 2001:db8:42::2 + + # Additional gateways for route-to. + rtgw=${net_server_host_server%::*}::2:1 + jexec router ndp -s ${rtgw} 00:01:02:03:04:05 + + # This test will check for proper source node creation for: + # max-src-states -> PF_SN_LIMIT + # sticky-address -> PF_SN_NAT + # route-to -> PF_SN_ROUTE + # The test expands to all 8 combinations of those source nodes being + # present or not. + + pft_set_rules router \ + "table { ${rtgw} }" \ + "table { 2001:db8:45::1 }" \ + "block" \ + "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ + "match in on ${epair_tester}b inet6 proto tcp from 2001:db8:44::10/124 to 2001:db8:45::1 rdr-to port 4242 sticky-address label rule_3" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) inet6 proto tcp from port 4211 keep state label rule_4" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) sticky-address inet6 proto tcp from port 4212 keep state label rule_5" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) inet6 proto tcp from port 4213 keep state (max-src-states 3 source-track rule) label rule_6" \ + "pass in quick on ${epair_tester}b route-to ( ${epair_server}a ) sticky-address inet6 proto tcp from port 4214 keep state (max-src-states 3 source-track rule) label rule_7" \ + "pass out quick on ${epair_server}a keep state" + + # We don't check if state limits are properly enforced, this is tested + # by other tests in this file. + # Source address will not match the NAT rule + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::01 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::02 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::03 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::04 --to 2001:db8:45::1 + # Source address will match the NAT rule + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::11 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::12 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::13 --to 2001:db8:45::1 + ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::14 --to 2001:db8:45::1 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvss | normalize_pfctl_s > $states + nodes=$(mktemp) || exit 1 + jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes + + echo " === states ===" + cat $states + echo " === nodes ===" + cat $nodes + echo " === end === " + + # Order of states in output is not guaranteed, find each one separately. + for state_regexp in \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::1\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::2\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, route sticky-address$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::3\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track$' \ + 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::4\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, route sticky-address$' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::11\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4, NAT/RDR sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::12\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, NAT/RDR sticky-address, route sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::13\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track, NAT/RDR sticky-address' \ + 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::14\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, NAT/RDR sticky-address, route sticky-address' \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # Order of source nodes in output is not guaranteed, find each one separately. + for node_regexp in \ + '2001:db8:44::2 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \ + '2001:db8:44::3 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \ + '2001:db8:44::4 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \ + '2001:db8:44::4 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \ + '2001:db8:44::11 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \ + '2001:db8:44::12 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \ + '2001:db8:44::12 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \ + '2001:db8:44::13 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \ + '2001:db8:44::13 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \ + '2001:db8:44::14 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \ + '2001:db8:44::14 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \ + '2001:db8:44::14 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \ + ; do + grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + done +} + +sn_types_pass_cleanup() +{ + pft_cleanup +} atf_init_test_cases() { @@ -411,5 +510,6 @@ atf_add_test_case "max_src_conn_rule" atf_add_test_case "max_src_states_rule" atf_add_test_case "max_src_states_global" - atf_add_test_case "route_to" + atf_add_test_case "sn_types_compat" + atf_add_test_case "sn_types_pass" }