diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -143,9 +143,18 @@ int match; /* XXX: used for pfctl black magic */ }; +struct pfctl_pooladdr { + struct pf_addr_wrap addr; + TAILQ_ENTRY(pfctl_pooladdr) entries; + char ifname[IFNAMSIZ]; + sa_family_t af; +}; + +TAILQ_HEAD(pfctl_palist, pfctl_pooladdr); + struct pfctl_pool { - struct pf_palist list; - struct pf_pooladdr *cur; + struct pfctl_palist list; + struct pfctl_pooladdr *cur; struct pf_poolhashkey key; struct pf_addr counter; struct pf_mape_portset mape; @@ -374,6 +383,7 @@ uint8_t set_prio[2]; uint8_t rt; char rt_ifname[IFNAMSIZ]; + sa_family_t rt_af; uint8_t src_node_flags; }; @@ -412,7 +422,7 @@ uint32_t states; uint32_t conn; sa_family_t af; - sa_family_t naf; + sa_family_t naf; // TODO: rename to r(edirection)af uint8_t ruletype; uint64_t creation; uint64_t expire; diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1914,6 +1914,7 @@ { .type = PF_ST_RT, .off = _OUT(rt), .cb = snl_attr_get_uint8 }, { .type = PF_ST_RT_IFNAME, .off = _OUT(rt_ifname), .cb = snl_attr_store_ifname }, { .type = PF_ST_SRC_NODE_FLAGS, .off = _OUT(src_node_flags), .cb = snl_attr_get_uint8 }, + { .type = PF_ST_RT_AF, .off = _OUT(rt_af), .cb = snl_attr_get_uint8 }, }; #undef _IN #undef _OUT diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -233,6 +233,7 @@ #define POM_TYPE 0x01 #define POM_STICKYADDRESS 0x02 #define POM_ENDPI 0x04 +#define POM_RFC5549 0x08 u_int8_t opts; int type; int staticport; @@ -244,7 +245,7 @@ struct node_host *host; struct range rport; struct pool_opts pool_opts; - int af; + sa_family_t af; /* Used only for af-to naf */ bool binat; }; @@ -531,7 +532,7 @@ %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 NATTO RDRTO -%token BINATTO +%token BINATTO RFC5549 %token STRING %token NUMBER %token PORTBINARY @@ -2736,13 +2737,16 @@ YYERROR; } r.rt = $5.rt; - decide_address_family($5.redirspec->host, &r.af); - if (!(r.rule_flag & PFRULE_AFTO)) - remove_invalid_hosts(&($5.redirspec->host), &r.af); - if ($5.redirspec->host == NULL) { - yyerror("no routing address with " - "matching address family found."); - YYERROR; + + if (!($5.redirspec->pool_opts.opts & PF_POOL_RFC5549)) { + decide_address_family($5.redirspec->host, &r.af); + if (!(r.rule_flag & PFRULE_AFTO)) + remove_invalid_hosts(&($5.redirspec->host), &r.af); + if ($5.redirspec->host == NULL) { + yyerror("no routing address with " + "matching address family found."); + YYERROR; + } } } if ($9.queues.qname != NULL) { @@ -3081,10 +3085,10 @@ yyerror("no address family specified"); YYERROR; } - filter_opts.nat = $4; filter_opts.nat->af = $2; - if ($4->af && $4->af != $2) { + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + if ($4->host == NULL) { yyerror("af-to addresses must be in the " "target address family"); YYERROR; @@ -3104,8 +3108,9 @@ filter_opts.nat->af = $2; filter_opts.rdr = $6; filter_opts.rdr->af = $2; - if (($4->af && $4->host->af != $2) || - ($6->af && $6->host->af != $2)) { + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + remove_invalid_hosts(&($6->host), &(filter_opts.rdr->af)); + if ($4->host == NULL || $6->host == NULL) { yyerror("af-to addresses must be in the " "target address family"); YYERROR; @@ -4748,6 +4753,14 @@ pool_opts.marker |= POM_ENDPI; pool_opts.opts |= PF_POOL_ENDPI; } + | RFC5549 { + if (pool_opts.marker & POM_RFC5549) { + yyerror("rfc5549 cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_RFC5549; + pool_opts.opts |= PF_POOL_RFC5549; + } | MAPEPORTSET number '/' number '/' number { if (pool_opts.mape.offset) { yyerror("map-e-portset cannot be redefined"); @@ -4887,6 +4900,13 @@ "address'"); YYERROR; } + if ($9->pool_opts.opts & PF_POOL_RFC5549) { + yyerror("The rfc5549 option can't be " + "used for nat/rdr/binat pools" + ); + YYERROR; + } + if (!r.af && ! $9->host->ifindex) r.af = $9->host->af; @@ -4926,7 +4946,7 @@ tagged rtable binat_redirspec { struct pfctl_rule binat; - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; @@ -5085,11 +5105,12 @@ YYERROR; } - pa = calloc(1, sizeof(struct pf_pooladdr)); + pa = calloc(1, sizeof(struct pfctl_pooladdr)); if (pa == NULL) err(1, "binat: calloc"); pa->addr = $13->host->addr; pa->ifname[0] = 0; + pa->af = $13->host->af; TAILQ_INSERT_TAIL(&binat.rdr.list, pa, entries); @@ -5147,13 +5168,6 @@ route_host_list : route_host optnl { $$ = $1; } | route_host_list comma route_host optnl { - if ($1->af == 0) - $1->af = $3->af; - if ($1->af != $3->af) { - yyerror("all pool addresses must be in the " - "same address family"); - YYERROR; - } $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; @@ -6160,7 +6174,7 @@ apply_redirspec(struct pfctl_pool *rpool, struct redirspec *rs) { struct node_host *h; - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; if (rs == NULL) return 0; @@ -6200,10 +6214,11 @@ sizeof(struct pf_poolhashkey)); for (h = rs->host; h != NULL; h = h->next) { - pa = calloc(1, sizeof(struct pf_pooladdr)); + pa = calloc(1, sizeof(struct pfctl_pooladdr)); if (pa == NULL) err(1, "expand_rule: calloc"); pa->addr = h->addr; + pa->af = h->af; if (h->ifname != NULL) { if (strlcpy(pa->ifname, h->ifname, sizeof(pa->ifname)) >= sizeof(pa->ifname)) @@ -6219,7 +6234,7 @@ int check_binat_redirspec(struct node_host *src_host, struct pfctl_rule *r, int af) { - struct pf_pooladdr *nat_pool = TAILQ_FIRST(&(r->nat.list)); + struct pfctl_pooladdr *nat_pool = TAILQ_FIRST(&(r->nat.list)); int error = 0; /* XXX: FreeBSD allows syntax like "{ host1 host2 }" for redirection @@ -6292,7 +6307,7 @@ /* * We're copying the whole rule, but we must re-init redir pools. - * FreeBSD uses lists of pf_pooladdr, we can't just overwrite them. + * FreeBSD uses lists of pfctl_pooladdr, we can't just overwrite them. */ bcopy(binat_rule, rdr_rule, sizeof(struct pfctl_rule)); TAILQ_INIT(&(rdr_rule->rdr.list)); @@ -6740,6 +6755,7 @@ { "return-icmp", RETURNICMP}, { "return-icmp6", RETURNICMP6}, { "return-rst", RETURNRST}, + { "rfc5549", RFC5549}, { "ridentifier", RIDENTIFIER}, { "round-robin", ROUNDROBIN}, { "route", ROUTE}, diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c --- a/sbin/pfctl/pf_print_state.c +++ b/sbin/pfctl/pf_print_state.c @@ -126,10 +126,12 @@ if (addr->type != PF_ADDR_RANGE && !(PF_AZERO(&addr->v.a.addr, AF_INET6) && PF_AZERO(&addr->v.a.mask, AF_INET6))) { - int bits = unmask(&addr->v.a.mask, af); + if (af == AF_INET || af == AF_INET6) { + int bits = unmask(&addr->v.a.mask, af); - if (bits < (af == AF_INET ? 32 : 128)) - printf("/%d", bits); + if (bits < (af == AF_INET ? 32 : 128)) + printf("/%d", bits); + } } } @@ -241,7 +243,6 @@ struct pfctl_state_key *key, *sk, *nk; const char *protoname; int min, sec; - sa_family_t af; uint8_t proto; int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af); int idx; @@ -255,7 +256,6 @@ key = s->key; #endif - af = s->key[PF_SK_WIRE].af; proto = s->key[PF_SK_WIRE].proto; if (s->direction == PF_OUT) { @@ -443,7 +443,7 @@ default: printf(" gateway: "); } - print_host(&s->rt_addr, 0, af, opts); + print_host(&s->rt_addr, 0, s->rt_af, opts); if (s->rt_ifname[0]) printf("@%s", s->rt_ifname); } diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1058,7 +1058,7 @@ u_int32_t ticket, int r_action, const char *anchorname, int which) { struct pfioc_pooladdr pp; - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; u_int32_t pnr, mpnr; int ret; @@ -1074,10 +1074,11 @@ warnc(ret, "DIOCGETADDR"); return (-1); } - pa = calloc(1, sizeof(struct pf_pooladdr)); + pa = calloc(1, sizeof(struct pfctl_pooladdr)); if (pa == NULL) err(1, "calloc"); - bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); + bcopy(&pp.addr, pa, sizeof(struct pfctl_pooladdr)); + pa->af = pp.af; TAILQ_INSERT_TAIL(&pool->list, pa, entries); } @@ -1087,7 +1088,7 @@ void pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst) { - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; while ((pa = TAILQ_FIRST(&src->list)) != NULL) { TAILQ_REMOVE(&src->list, pa, entries); @@ -1098,7 +1099,7 @@ void pfctl_clear_pool(struct pfctl_pool *pool) { - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); @@ -1819,14 +1820,14 @@ /* callbacks for rule/nat/rdr/addr */ int -pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af, int which) +pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, int which) { - struct pf_pooladdr *pa; + struct pfctl_pooladdr *pa; int ret; - pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { - memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); + memcpy(&pf->paddr.addr, pa, sizeof(struct pfctl_pooladdr)); + pf->paddr.af = pa->af; if ((pf->opts & PF_OPT_NOACTION) == 0) { if ((ret = pfctl_add_addr(pf->h, &pf->paddr, which)) != 0) errc(1, ret, "DIOCADDADDR"); @@ -2191,11 +2192,11 @@ errc(1, error, "DIOCBEGINADDRS"); } - if (pfctl_add_pool(pf, &r->rdr, r->af, PF_RDR)) + if (pfctl_add_pool(pf, &r->rdr, PF_RDR)) return (1); - if (pfctl_add_pool(pf, &r->nat, r->naf ? r->naf : r->af, PF_NAT)) + if (pfctl_add_pool(pf, &r->nat, PF_NAT)) return (1); - if (pfctl_add_pool(pf, &r->route, r->af, PF_RT)) + if (pfctl_add_pool(pf, &r->route, PF_RT)) return (1); error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket, pf->paddr.ticket); diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -283,7 +283,7 @@ int pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *, const char *); int pfctl_add_altq(struct pfctl *, struct pf_altq *); -int pfctl_add_pool(struct pfctl *, struct pfctl_pool *, sa_family_t, int); +int pfctl_add_pool(struct pfctl *, struct pfctl_pool *, int); void pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *); void pfctl_clear_pool(struct pfctl_pool *); @@ -301,7 +301,7 @@ int parse_flags(char *); int pfctl_load_anchors(int, struct pfctl *, struct pfr_buffer *); -void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, sa_family_t, int); +void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, int); void print_src_node(struct pfctl_src_node *, int); void print_eth_rule(struct pfctl_eth_rule *, const char *, int); void print_rule(struct pfctl_rule *, const char *, int, int); 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 @@ -418,10 +418,9 @@ } void -print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, - sa_family_t af, int id) +print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, int id) { - struct pf_pooladdr *pooladdr; + struct pfctl_pooladdr *pooladdr; if ((TAILQ_FIRST(&pool->list) != NULL) && TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) @@ -431,15 +430,15 @@ case PF_NAT: case PF_RDR: case PF_BINAT: - print_addr(&pooladdr->addr, af, 0); + print_addr(&pooladdr->addr, pooladdr->af, 0); break; case PF_PASS: case PF_MATCH: - if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) + if (PF_AZERO(&pooladdr->addr.v.a.addr, pooladdr->af)) // FIXME printf("%s", pooladdr->ifname); else { printf("(%s ", pooladdr->ifname); - print_addr(&pooladdr->addr, af, 0); + print_addr(&pooladdr->addr, pooladdr->af, 0); printf(")"); } break; @@ -498,6 +497,8 @@ if (pool->mape.offset > 0) printf(" map-e-portset %u/%u/%u", pool->mape.offset, pool->mape.psidlen, pool->mape.psid); + if (pool->opts & PF_POOL_RFC5549) + printf(" rfc5549"); } void @@ -663,7 +664,7 @@ print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); printf(" -> "); aw.v.a.addr = sn->raddr; - print_addr(&aw, sn->naf ? sn->naf : sn->af, opts & PF_OPT_VERBOSE2); + print_addr(&aw, sn->naf, opts & PF_OPT_VERBOSE2); printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states, sn->conn, sn->conn_rate.count / 1000, (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds); @@ -941,10 +942,7 @@ else if (r->rt == PF_DUPTO) printf(" dup-to"); printf(" "); - print_pool(&r->rdr, 0, 0, r->af, PF_PASS); - print_pool(&r->route, 0, 0, - r->rule_flag & PFRULE_AFTO && r->rt != PF_REPLYTO ? r->naf : r->af, - PF_PASS); + print_pool(&r->route, 0, 0, PF_PASS); } if (r->af) { if (r->af == AF_INET) @@ -1245,17 +1243,16 @@ 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); + r->rdr.proxy_port[1], 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"); + printf(" af-to %s from ", r->naf == AF_INET ? "inet" : (r->naf == AF_INET6 ? "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); + r->nat.proxy_port[1], PF_NAT); } if (!TAILQ_EMPTY(&r->rdr.list)) { if (r->rule_flag & PFRULE_AFTO) { @@ -1264,8 +1261,7 @@ 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); + r->rdr.proxy_port[1], PF_RDR); } } } diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -657,6 +657,7 @@ struct pf_addr_wrap addr; TAILQ_ENTRY(pf_kpooladdr) entries; char ifname[IFNAMSIZ]; + sa_family_t af; struct pfi_kkif *kif; }; @@ -672,6 +673,7 @@ int tblidx; u_int16_t proxy_port[2]; u_int8_t opts; + sa_family_t rfc5549_af; }; struct pf_rule_actions { @@ -684,6 +686,7 @@ uint16_t max_mss; uint16_t dnpipe; uint16_t dnrpipe; /* Reverse direction pipe */ + sa_family_t rt_af; uint8_t log; uint8_t set_tos; uint8_t min_ttl; @@ -930,7 +933,7 @@ u_int32_t creation; u_int32_t expire; sa_family_t af; - sa_family_t naf; + sa_family_t raf; u_int8_t ruletype; pf_sn_types_t type; struct mtx *lock; @@ -2729,14 +2732,15 @@ struct pf_keth_rule **, struct pf_keth_rule **, int *); -u_short pf_map_addr(u_int8_t, struct pf_krule *, +u_short pf_map_addr(sa_family_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, - struct pfi_kkif **nkif, struct pf_addr *, - struct pf_kpool *); + struct pfi_kkif **nkif, sa_family_t *, + struct pf_addr *, struct pf_kpool *); u_short pf_map_addr_sn(u_int8_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, - struct pfi_kkif **nkif, struct pf_addr *, - struct pf_kpool *, pf_sn_types_t); + sa_family_t *, struct pfi_kkif **, + struct pf_addr *, struct pf_kpool *, + pf_sn_types_t); int pf_get_transaddr_af(struct pf_krule *, struct pf_pdesc *); u_short pf_get_translation(struct pf_test_ctx *); diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c --- a/sys/netpfil/pf/if_pfsync.c +++ b/sys/netpfil/pf/if_pfsync.c @@ -529,6 +529,7 @@ struct pfi_kkif *rt_kif = NULL; struct pf_kpooladdr *rpool_first; int error; + sa_family_t rt_af = 0; uint8_t rt = 0; PF_RULES_RASSERT(); @@ -599,6 +600,12 @@ } rt = r->rt; rt_kif = rpool_first->kif; + /* + * Guess the AF of the route address, FreeBSD 13 does + * not support rfc5549 on route-to pools, so it should + * be safe. + */ + rt_af = r->af; } else if (!PF_AZERO(&sp->pfs_1301.rt_addr, sp->pfs_1301.af)) { /* * Ruleset different, routing *supposedly* requested, @@ -624,6 +631,12 @@ return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0); } rt = sp->pfs_1400.rt; + /* + * Guess the AF of the route address, FreeBSD 14 does + * not support rfc5549 on route-to pools, so it should + * be safe. + */ + rt_af = sp->pfs_1400.af; } break; } @@ -703,6 +716,7 @@ st->act.rt = rt; st->act.rt_kif = rt_kif; + st->act.rt_af = rt_af; switch (msg_version) { case PFSYNC_MSG_VERSION_1301: diff --git a/sys/netpfil/pf/pf.h b/sys/netpfil/pf/pf.h --- a/sys/netpfil/pf/pf.h +++ b/sys/netpfil/pf/pf.h @@ -130,6 +130,7 @@ #define PF_POOL_TYPEMASK 0x0f #define PF_POOL_STICKYADDR 0x20 #define PF_POOL_ENDPI 0x40 +#define PF_POOL_RFC5549 0x80 #define PF_WSCALE_FLAG 0x80 #define PF_WSCALE_MASK 0x0f 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 @@ -401,7 +401,7 @@ static u_short pf_insert_src_node(struct pf_ksrc_node *[PF_SN_MAX], struct pf_srchash *[PF_SN_MAX], struct pf_krule *, struct pf_addr *, sa_family_t, struct pf_addr *, - struct pfi_kkif *, pf_sn_types_t); + struct pfi_kkif *, sa_family_t, pf_sn_types_t); static u_int pf_purge_expired_states(u_int, int); static void pf_purge_unlinked_rules(void); static int pf_mtag_uminit(void *, int, int); @@ -1038,7 +1038,7 @@ pf_insert_src_node(struct pf_ksrc_node *sns[PF_SN_MAX], struct pf_srchash *snhs[PF_SN_MAX], struct pf_krule *rule, struct pf_addr *src, sa_family_t af, struct pf_addr *raddr, - struct pfi_kkif *rkif, pf_sn_types_t sn_type) + struct pfi_kkif *rkif, sa_family_t raf, pf_sn_types_t sn_type) { u_short reason = 0; struct pf_krule *r_track = rule; @@ -1106,8 +1106,9 @@ (*sn)->rule = r_track; PF_ACPY(&(*sn)->addr, src, af); if (raddr != NULL) - PF_ACPY(&(*sn)->raddr, raddr, af); + PF_ACPY(&(*sn)->raddr, raddr, raf); (*sn)->rkif = rkif; + (*sn)->raf = raf; LIST_INSERT_HEAD(&(*sh)->nodes, *sn, entry); (*sn)->creation = time_uptime; (*sn)->ruletype = rule->action; @@ -5872,9 +5873,14 @@ * it is applied only from the last pass rule. */ pd->act.rt = r->rt; + /* For rfc5549 routes rt_af is a hint and will be overwritten */ + if (r->rt == PF_REPLYTO) + pd->act.rt_af = pd->af; + else + pd->act.rt_af = pd->naf ? pd->naf : pd->af; if ((transerror = pf_map_addr_sn(pd->af, r, pd->src, - &pd->act.rt_addr, &pd->act.rt_kif, NULL, &(r->route), - PF_SN_ROUTE)) != PFRES_MATCH) { + &pd->act.rt_addr, &pd->act.rt_af, &pd->act.rt_kif, NULL, + &(r->route), PF_SN_ROUTE)) != PFRES_MATCH) { REASON_SET(&ctx.reason, transerror); goto cleanup; } @@ -6004,7 +6010,7 @@ /* src node for limits */ if ((r->rule_flag & PFRULE_SRCTRACK) && (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, pd->af, - NULL, NULL, PF_SN_LIMIT)) != 0) { + NULL, NULL, pd->af, PF_SN_LIMIT)) != 0) { REASON_SET(&ctx->reason, sn_reason); goto csfailed; } @@ -6012,7 +6018,7 @@ if (r->rt) { if ((r->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, + pd->af, &pd->act.rt_addr, pd->act.rt_kif, pd->act.rt_af, PF_SN_ROUTE)) != 0) { REASON_SET(&ctx->reason, sn_reason); goto csfailed; @@ -6031,7 +6037,7 @@ (sn_reason = pf_insert_src_node(sns, snhs, ctx->nr, ctx->sk ? &(ctx->sk->addr[pd->sidx]) : pd->src, pd->af, ctx->nk ? &(ctx->nk->addr[1]) : &(pd->nsaddr), NULL, - PF_SN_NAT)) != 0 ) { + pd->naf, PF_SN_NAT)) != 0 ) { REASON_SET(&ctx->reason, sn_reason); goto csfailed; } @@ -8845,9 +8851,10 @@ struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp) { struct mbuf *m0, *m1, *md; - struct route ro; - const struct sockaddr *gw = &ro.ro_dst; - struct sockaddr_in *dst; + struct route_in6 ro; + union sockaddr_union rt_gw; + const union sockaddr_union *gw = (const union sockaddr_union *)&ro.ro_dst; + union sockaddr_union *dst; struct ip *ip; struct ifnet *ifp = NULL; int error = 0; @@ -8939,10 +8946,34 @@ ip = mtod(m0, struct ip *); bzero(&ro, sizeof(ro)); - dst = (struct sockaddr_in *)&ro.ro_dst; - dst->sin_family = AF_INET; - dst->sin_len = sizeof(struct sockaddr_in); - dst->sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; + dst = (union sockaddr_union *)&ro.ro_dst; + dst->sin.sin_family = AF_INET; + dst->sin.sin_len = sizeof(struct sockaddr_in); + dst->sin.sin_addr = ip->ip_dst; + if (ifp) { /* Only needed in forward direction and route-to */ + bzero(&rt_gw, sizeof(rt_gw)); + ro.ro_flags |= RT_HAS_GW; + gw = &rt_gw; + switch (pd->act.rt_af) { +#ifdef INET + case AF_INET: + rt_gw.sin.sin_family = AF_INET; + rt_gw.sin.sin_len = sizeof(struct sockaddr_in); + rt_gw.sin.sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + rt_gw.sin6.sin6_family = AF_INET6; + rt_gw.sin6.sin6_len = sizeof(struct sockaddr_in6); + PF_ACPY((struct pf_addr *)&rt_gw.sin6.sin6_addr, &pd->act.rt_addr, AF_INET6); + break; +#endif /* INET6 */ + default: + /* Normal af-to without route-to */ + break; + } + } if (s != NULL){ if (ifp == NULL && (pd->af != pd->naf)) { @@ -8954,10 +8985,10 @@ /* Use the gateway if needed. */ if (nh->nh_flags & NHF_GATEWAY) { - gw = &nh->gw_sa; + gw = (const union sockaddr_union *)&nh->gw_sa; ro.ro_flags |= RT_HAS_GW; } else { - dst->sin_addr = ip->ip_dst; + dst->sin.sin_addr = ip->ip_dst; } /* @@ -8982,6 +9013,9 @@ PF_STATE_UNLOCK(s); } + /* It must have been either set from rt_af or from fib4_lookup */ + KASSERT(gw->sin.sin_family != 0, ("%s: gw address family undetermined", __func__)); + if (ifp == NULL) { m0 = pd->m; pd->m = NULL; @@ -9059,9 +9093,9 @@ m_clrprotoflags(m0); /* Avoid confusing lower layers. */ md = m0; - error = pf_dummynet_route(pd, s, r, ifp, gw, &md); + error = pf_dummynet_route(pd, s, r, ifp, (const struct sockaddr *)gw, &md); if (md != NULL) { - error = (*ifp->if_output)(ifp, md, gw, &ro); + error = (*ifp->if_output)(ifp, md, (const struct sockaddr *)gw, (struct route *)&ro); SDT_PROBE2(pf, ip, route_to, output, ifp, error); } goto done; @@ -9102,10 +9136,9 @@ m_clrprotoflags(m0); md = m0; pd->pf_mtag = pf_find_mtag(md); - error = pf_dummynet_route(pd, s, r, ifp, - gw, &md); + error = pf_dummynet_route(pd, s, r, ifp, (const struct sockaddr *)gw, &md); if (md != NULL) { - error = (*ifp->if_output)(ifp, md, gw, &ro); + error = (*ifp->if_output)(ifp, md, (const struct sockaddr *)gw, (struct route *)&ro); SDT_PROBE2(pf, ip, route_to, output, ifp, error); } } else diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -2252,6 +2252,7 @@ rule->nat.cur = TAILQ_FIRST(&rule->nat.list); rule->rdr.cur = TAILQ_FIRST(&rule->rdr.list); rule->route.cur = TAILQ_FIRST(&rule->route.list); + rule->route.rfc5549_af = AF_INET6; TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount++; @@ -2636,6 +2637,7 @@ PF_RULES_WUNLOCK(); goto out; } + pa->af = pp->af; switch (pp->which) { case PF_NAT: TAILQ_INSERT_TAIL(&V_pf_pabuf[0], pa, entries); @@ -2717,6 +2719,7 @@ return (EBUSY); } pf_kpooladdr_to_pooladdr(pa, &pp->addr); + pp->af = pa->af; pf_addr_copyout(&pp->addr.addr); PF_RULES_RUNLOCK(); 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 @@ -345,8 +345,8 @@ } } - if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, NULL, &init_addr, - rpool, sn_type)) + if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, &(pd->naf), NULL, + &init_addr, rpool, sn_type)) goto failed; if (pd->proto == IPPROTO_ICMP) { @@ -469,8 +469,8 @@ * pick a different source address since we're out * of free port choices for the current one. */ - if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, NULL, - &init_addr, rpool, sn_type)) + if (pf_map_addr_sn(pd->naf, r, &pd->nsaddr, naddr, + &(pd->naf), NULL, &init_addr, rpool, sn_type)) return (1); break; case PF_POOL_NONE: @@ -500,8 +500,8 @@ static int pf_get_mape_sport(struct pf_pdesc *pd, struct pf_krule *r, - struct pf_addr *naddr, uint16_t *nport, - struct pf_udp_mapping **udp_mapping, struct pf_kpool *rpool) + struct pf_addr *naddr, uint16_t *nport, struct pf_udp_mapping **udp_mapping, + struct pf_kpool *rpool) { uint16_t psmask, low, highmask; uint16_t i, ahigh, cut; @@ -534,14 +534,22 @@ } u_short -pf_map_addr(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr, - struct pf_addr *naddr, struct pfi_kkif **nkif, struct pf_addr *init_addr, - struct pf_kpool *rpool) +pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr, + struct pf_addr *naddr, struct pfi_kkif **nkif, sa_family_t *naf, + struct pf_addr *init_addr, struct pf_kpool *rpool) { u_short reason = PFRES_MATCH; struct pf_addr *raddr = NULL, *rmask = NULL; uint64_t hashidx; int cnt; + sa_family_t wanted_af; + bool rfc5549 = rpool->opts & PF_POOL_RFC5549; + + KASSERT(saf != 0, ("%s: saf == 0", __func__)); + KASSERT(naf != NULL, ("%s: naf = NULL", __func__)); + KASSERT((*naf) != 0, ("%s: *naf = 0", __func__)); + + wanted_af = (*naf); mtx_lock(&rpool->mtx); /* Find the route using chosen algorithm. Store the found route @@ -551,7 +559,7 @@ goto done_pool_mtx; } if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { - switch (af) { + switch (wanted_af) { #ifdef INET case AF_INET: if (rpool->cur->addr.p.dyn->pfid_acnt4 < 1 && @@ -575,7 +583,7 @@ break; #endif /* INET6 */ default: - unhandled_af(af); + unhandled_af(wanted_af); } } else if (rpool->cur->addr.type == PF_ADDR_TABLE) { if (!PF_POOL_DYNTYPE(rpool->opts)) { @@ -589,10 +597,26 @@ switch (rpool->opts & PF_POOL_TYPEMASK) { case PF_POOL_NONE: - PF_ACPY(naddr, raddr, af); + /* + * For RFC5549 we can return pool address of any AF, unless + * the forwarded packet is IPv6, then we can return only if + * pool address is IPv6. + * For non-RFC5549 we can return pool address only of wanted + * AF, unless the pool address'es AF is unknown, which happens + * in case old ioctls have been used to set up the pool. + */ + if ((rfc5549 && rpool->cur->af == AF_INET && wanted_af == AF_INET6) + || (!rfc5549 && rpool->cur->af != 0 && rpool->cur->af != wanted_af)) { + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } + if (rfc5549) { + wanted_af = rpool->cur->af; + } + PF_ACPY(naddr, raddr, wanted_af); break; case PF_POOL_BITMASK: - PF_POOLMASK(naddr, raddr, rmask, saddr, af); + PF_POOLMASK(naddr, raddr, rmask, saddr, saf); break; case PF_POOL_RANDOM: if (rpool->cur->addr.type == PF_ADDR_TABLE) { @@ -603,11 +627,12 @@ rpool->tblidx = (int)arc4random_uniform(cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); if (pfr_pool_get(rpool->cur->addr.p.tbl, - &rpool->tblidx, &rpool->counter, af, NULL, false)) { + &rpool->tblidx, &rpool->counter, wanted_af, NULL, + false)) { reason = PFRES_MAPFAILED; goto done_pool_mtx; /* unsupported */ } - PF_ACPY(naddr, &rpool->counter, af); + PF_ACPY(naddr, &rpool->counter, wanted_af); } else if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { cnt = rpool->cur->addr.p.dyn->pfid_kt->pfrkt_cnt; if (cnt == 0) @@ -616,14 +641,14 @@ rpool->tblidx = (int)arc4random_uniform(cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); if (pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, - &rpool->tblidx, &rpool->counter, af, + &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, false)) { reason = PFRES_MAPFAILED; goto done_pool_mtx; /* unsupported */ } - PF_ACPY(naddr, &rpool->counter, af); - } else if (init_addr != NULL && PF_AZERO(init_addr, af)) { - switch (af) { + PF_ACPY(naddr, &rpool->counter, wanted_af); + } else if (init_addr != NULL && PF_AZERO(init_addr, wanted_af)) { + switch (wanted_af) { #ifdef INET case AF_INET: rpool->counter.addr32[0] = arc4random(); @@ -652,12 +677,12 @@ break; #endif /* INET6 */ } - PF_POOLMASK(naddr, raddr, rmask, &rpool->counter, af); - PF_ACPY(init_addr, naddr, af); + PF_POOLMASK(naddr, raddr, rmask, &rpool->counter, wanted_af); + PF_ACPY(init_addr, naddr, wanted_af); } else { - PF_AINC(&rpool->counter, af); - PF_POOLMASK(naddr, raddr, rmask, &rpool->counter, af); + PF_AINC(&rpool->counter, wanted_af); + PF_POOLMASK(naddr, raddr, rmask, &rpool->counter, wanted_af); } break; case PF_POOL_SRCHASH: @@ -665,7 +690,7 @@ unsigned char hash[16]; hashidx = - pf_hash(saddr, (struct pf_addr *)&hash, &rpool->key, af); + pf_hash(saddr, (struct pf_addr *)&hash, &rpool->key, saf); if (rpool->cur->addr.type == PF_ADDR_TABLE) { cnt = rpool->cur->addr.p.tbl->pfrkt_cnt; if (cnt == 0) @@ -674,11 +699,12 @@ rpool->tblidx = (int)(hashidx % cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); if (pfr_pool_get(rpool->cur->addr.p.tbl, - &rpool->tblidx, &rpool->counter, af, NULL, false)) { + &rpool->tblidx, &rpool->counter, wanted_af, NULL, + false)) { reason = PFRES_MAPFAILED; goto done_pool_mtx; /* unsupported */ } - PF_ACPY(naddr, &rpool->counter, af); + PF_ACPY(naddr, &rpool->counter, wanted_af); } else if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { cnt = rpool->cur->addr.p.dyn->pfid_kt->pfrkt_cnt; if (cnt == 0) @@ -687,77 +713,101 @@ rpool->tblidx = (int)(hashidx % cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); if (pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, - &rpool->tblidx, &rpool->counter, af, + &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, false)) { reason = PFRES_MAPFAILED; goto done_pool_mtx; /* unsupported */ } - PF_ACPY(naddr, &rpool->counter, af); + PF_ACPY(naddr, &rpool->counter, wanted_af); } else { PF_POOLMASK(naddr, raddr, rmask, - (struct pf_addr *)&hash, af); + (struct pf_addr *)&hash, wanted_af); } break; } + case PF_POOL_ROUNDROBIN: { struct pf_kpooladdr *acur = rpool->cur; - + try_rfc5549: + if (rfc5549) + wanted_af = rpool->rfc5549_af; if (rpool->cur->addr.type == PF_ADDR_TABLE) { if (!pfr_pool_get(rpool->cur->addr.p.tbl, - &rpool->tblidx, &rpool->counter, af, NULL, true)) + &rpool->tblidx, &rpool->counter, wanted_af, NULL, + true)) goto get_addr; } else if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { if (!pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, - &rpool->tblidx, &rpool->counter, af, pf_islinklocal, - true)) + &rpool->tblidx, &rpool->counter, wanted_af, + pf_islinklocal, true)) goto get_addr; - } else if (pf_match_addr(0, raddr, rmask, &rpool->counter, af)) + } else if (rpool->cur->af == wanted_af && + pf_match_addr(0, raddr, rmask, &rpool->counter, wanted_af)) goto get_addr; - + if (rfc5549 && (*naf) == AF_INET && wanted_af == AF_INET6) { + /* Reset table index when changing wanted AF. */ + rpool->tblidx = -1; + rpool->rfc5549_af = AF_INET; + goto try_rfc5549; + } try_next: + /* Reset rfc5549 search to IPv6 when iterating pools. */ + rpool->rfc5549_af = AF_INET6; if (TAILQ_NEXT(rpool->cur, entries) == NULL) rpool->cur = TAILQ_FIRST(&rpool->list); else rpool->cur = TAILQ_NEXT(rpool->cur, entries); + try_next_rfc5549: + /* Reset table index when iterating pools or changing wanted AF. */ rpool->tblidx = -1; + if (rfc5549) + wanted_af = rpool->rfc5549_af; if (rpool->cur->addr.type == PF_ADDR_TABLE) { - if (pfr_pool_get(rpool->cur->addr.p.tbl, - &rpool->tblidx, &rpool->counter, af, NULL, true)) { - /* table contains no address of type 'af' */ - if (rpool->cur != acur) - goto try_next; - reason = PFRES_MAPFAILED; - goto done_pool_mtx; - } + if (!pfr_pool_get(rpool->cur->addr.p.tbl, + &rpool->tblidx, &rpool->counter, wanted_af, NULL, + true)) + goto get_addr; } else if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { - if (pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, - &rpool->tblidx, &rpool->counter, af, pf_islinklocal, - true)) { - /* table contains no address of type 'af' */ - if (rpool->cur != acur) - goto try_next; - reason = PFRES_MAPFAILED; - goto done_pool_mtx; - } + if (!pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, + &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, + true)) + goto get_addr; } else { - raddr = &rpool->cur->addr.v.a.addr; - rmask = &rpool->cur->addr.v.a.mask; - PF_ACPY(&rpool->counter, raddr, af); + if (rpool->cur->af == wanted_af) { + raddr = &rpool->cur->addr.v.a.addr; + rmask = &rpool->cur->addr.v.a.mask; + PF_ACPY(&rpool->counter, raddr, wanted_af); + goto get_addr; + } } - + if (rfc5549 && (*naf) == AF_INET && wanted_af == AF_INET6) { + rpool->rfc5549_af = AF_INET; + goto try_next_rfc5549; + } + if (rpool->cur != acur) + goto try_next; + reason = PFRES_MAPFAILED; + goto done_pool_mtx; get_addr: - PF_ACPY(naddr, &rpool->counter, af); - if (init_addr != NULL && PF_AZERO(init_addr, af)) - PF_ACPY(init_addr, naddr, af); - PF_AINC(&rpool->counter, af); + PF_ACPY(naddr, &rpool->counter, wanted_af); + if (init_addr != NULL && PF_AZERO(init_addr, wanted_af)) + PF_ACPY(init_addr, naddr, wanted_af); + PF_AINC(&rpool->counter, wanted_af); break; } } + if (wanted_af == 0) { + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } + if (nkif) *nkif = rpool->cur->kif; + (*naf) = wanted_af; + done_pool_mtx: mtx_unlock(&rpool->mtx); @@ -765,9 +815,9 @@ } u_short -pf_map_addr_sn(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr, - struct pf_addr *naddr, struct pfi_kkif **nkif, struct pf_addr *init_addr, - struct pf_kpool *rpool, pf_sn_types_t sn_type) +pf_map_addr_sn(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr, + struct pf_addr *naddr, sa_family_t *naf, struct pfi_kkif **nkif, + struct pf_addr *init_addr, struct pf_kpool *rpool, pf_sn_types_t sn_type) { struct pf_ksrc_node *sn = NULL; struct pf_srchash *sh = NULL; @@ -778,27 +828,30 @@ */ if (rpool->opts & PF_POOL_STICKYADDR && (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) - sn = pf_find_src_node(saddr, r, af, &sh, sn_type, false); + sn = pf_find_src_node(saddr, r, saf, &sh, sn_type, false); if (sn != NULL) { PF_SRC_NODE_LOCK_ASSERT(sn); + (*naf) = sn->raf; /* If the supplied address is the same as the current one we've * been asked before, so tell the caller that there's no other * address to be had. */ - if (PF_AEQ(naddr, &(sn->raddr), af)) { + + if (PF_AEQ(naddr, &(sn->raddr), *naf)) { + printf("%s: no more addresses\n", __func__); reason = PFRES_MAPFAILED; goto done; } - PF_ACPY(naddr, &(sn->raddr), af); + PF_ACPY(naddr, &(sn->raddr), *naf); if (nkif) *nkif = sn->rkif; if (V_pf_status.debug >= PF_DEBUG_NOISY) { printf("%s: src tracking maps ", __func__); - pf_print_host(saddr, 0, af); + pf_print_host(saddr, 0, saf); printf(" to "); - pf_print_host(naddr, 0, af); + pf_print_host(naddr, 0, *naf); if (nkif) printf("@%s", (*nkif)->pfik_name); printf("\n"); @@ -810,7 +863,7 @@ * Source node has not been found. Find a new address and store it * in variables given by the caller. */ - if ((reason = pf_map_addr(af, r, saddr, naddr, nkif, init_addr, + if ((reason = pf_map_addr(saf, r, saddr, naddr, nkif, naf, init_addr, rpool)) != 0) { printf("%s: pf_map_addr has failed\n", __func__); goto done; @@ -819,7 +872,7 @@ if (V_pf_status.debug >= PF_DEBUG_NOISY && (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) { printf("%s: selected address ", __func__); - pf_print_host(naddr, 0, af); + pf_print_host(naddr, 0, *naf); if (nkif) printf("@%s", (*nkif)->pfik_name); printf("\n"); @@ -1001,8 +1054,9 @@ int tries; uint16_t cut, low, high, nport; - reason = pf_map_addr_sn(pd->af, r, &pd->nsaddr, naddr, NULL, - NULL, rpool, PF_SN_NAT); + reason = pf_map_addr_sn(pd->af, r, &pd->nsaddr, naddr, + &(pd->naf), NULL, NULL, rpool, PF_SN_NAT); + if (reason != 0) goto notrans; if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK) @@ -1163,8 +1217,8 @@ /* get the destination address and port */ if (! TAILQ_EMPTY(&r->rdr.list)) { - if (pf_map_addr_sn(pd->naf, r, &nsaddr, &naddr, NULL, NULL, - &r->rdr, PF_SN_NAT)) + if (pf_map_addr_sn(pd->naf, r, &nsaddr, &naddr, &(pd->naf), + NULL, NULL, &r->rdr, PF_SN_NAT)) return (-1); if (r->rdr.proxy_port[0]) pd->ndport = htons(r->rdr.proxy_port[0]); diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -135,6 +135,7 @@ PF_ST_RT = 36, /* u8 */ PF_ST_RT_IFNAME = 37, /* string */ PF_ST_SRC_NODE_FLAGS = 38, /* u8 */ + PF_ST_RT_AF = 39, /* u8 */ }; enum pf_addr_type_t { diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -175,7 +175,7 @@ nlattr_add_string(nw, PF_ST_IFNAME, s->kif->pfik_name); nlattr_add_string(nw, PF_ST_ORIG_IFNAME, s->orig_kif->pfik_name); - dump_addr(nw, PF_ST_RT_ADDR, &s->act.rt_addr, af); + dump_addr(nw, PF_ST_RT_ADDR, &s->act.rt_addr, s->act.rt_af); nlattr_add_u32(nw, PF_ST_CREATION, time_uptime - (s->creation / 1000)); uint32_t expire = pf_state_expires(s); if (expire > time_uptime) @@ -221,6 +221,7 @@ if (s->sns[PF_SN_ROUTE] != NULL) src_node_flags |= PFSTATE_SRC_NODE_ROUTE; nlattr_add_u8(nw, PF_ST_SRC_NODE_FLAGS, src_node_flags); + nlattr_add_u8(nw, PF_ST_RT_AF, s->act.rt_af); if (!dump_state_peer(nw, PF_ST_PEER_SRC, &s->src)) goto enomem; @@ -1793,7 +1794,7 @@ nlattr_add_u32(nw, PF_SN_STATES, n->states); nlattr_add_u32(nw, PF_SN_CONNECTIONS, n->conn); nlattr_add_u8(nw, PF_SN_AF, n->af); - nlattr_add_u8(nw, PF_SN_NAF, n->naf); + nlattr_add_u8(nw, PF_SN_NAF, n->raf); nlattr_add_u8(nw, PF_SN_RULE_TYPE, n->ruletype); nlattr_add_u64(nw, PF_SN_CREATION, secs - n->creation); diff --git a/tests/sys/netpfil/pf/nat64.sh b/tests/sys/netpfil/pf/nat64.sh --- a/tests/sys/netpfil/pf/nat64.sh +++ b/tests/sys/netpfil/pf/nat64.sh @@ -26,6 +26,8 @@ . $(atf_get_srcdir)/utils.subr +common_dir=$(atf_get_srcdir)/../common + nat64_setup_base() { pft_init diff --git a/tests/sys/netpfil/pf/route_to.sh b/tests/sys/netpfil/pf/route_to.sh --- a/tests/sys/netpfil/pf/route_to.sh +++ b/tests/sys/netpfil/pf/route_to.sh @@ -875,7 +875,6 @@ jexec router pfctl -e pft_set_rules router \ - "set debug loud" \ "set reassemble yes" \ "set state-policy if-bound" \ "table { ${net_server1_6}::42:4/127 ${net_server1_6}::42:0/127 }" \ @@ -924,6 +923,321 @@ pft_cleanup } +rfc5549_setup_common() +{ + setup_router_server_nat64 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} + jexec router route add ${net_clients_4}.0/${net_clients_4_mask} ${net_tester_4_host_tester} + + # The servers are reachable over additional IP addresses for + # testing of tables and subnets. The addresses are noncontinougnus + # for pf_map_addr() counter tests. + for i in 0 1 4 5; do + a1=$((24 + i)) + jexec server1 ifconfig ${epair_server1}b inet ${net_server1_4}.${a1}/32 alias + jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:${i}/128 alias + a2=$((40 + i)) + jexec server2 ifconfig ${epair_server2}b inet ${net_server2_4}.${a2}/32 alias + jexec server2 ifconfig ${epair_server2}b inet6 ${net_server2_6}::42:${i}/128 alias + done +} + +atf_test_case "rfc5549_single_ipv4" "cleanup" + +rfc5549_single_ipv4_head() +{ + atf_set descr 'RFC5549 single IPv4 gateway' + atf_set require.user root +} + +rfc5549_single_ipv4_body() +{ + rfc5549_setup_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_4_host_server}) rfc5549 \ + proto tcp \ + keep state" + + # An IPv4 gateway will work for IPv4 traffic … + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + # … but not for IPv6. + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_4_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # The IPv6 packet could not have been routed over IPv4 gateway. + jexec router pfctl -qvvsi | grep -qE 'map-failed\s+1\s+' || atf_fail "map-failed not set to 1" +} + +rfc5549_single_ipv4_cleanup() +{ + pft_cleanup +} + +atf_test_case "rfc5549_single_ipv6" "cleanup" + +rfc5549_single_ipv6_head() +{ + atf_set descr 'RFC5549 single IPv6 gateway' + atf_set require.user root +} + +rfc5549_single_ipv6_body() +{ + rfc5549_setup_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_6_host_server}) rfc5549 \ + proto tcp \ + keep state" + + # This single gateway work both for IPv4 and IPv6 traffic. + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +rfc5549_single_ipv6_cleanup() +{ + pft_cleanup +} + +atf_test_case "rfc5549_mixed_af" "cleanup" + +rfc5549_mixed_af_head() +{ + atf_set descr 'RFC5549 multiple gateways of mixed AF' + atf_set require.user root +} + +rfc5549_mixed_af_body() +{ + rfc5549_setup_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server2}a ${net_server2_4_host_server}) \ + } rfc5549 \ + inet proto tcp \ + keep state" + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding rfc5549 support, + # we test for it. We also test that the pool loops back to beginning. + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4202 + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4203 + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4204 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server2_4_host_server}@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4202 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4203 .* route-to: ${net_server2_4_host_server}@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4204 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +rfc5549_mixed_af_subnet_cleanup() +{ + pft_cleanup +} + +atf_test_case "rfc5549_mixed_af_subnet" "cleanup" + +rfc5549_mixed_af_subnet_head() +{ + atf_set descr 'RFC5549 multiple gateways of mixed AF with a subnet' + atf_set require.user root +} + +rfc5549_mixed_af_subnet_body() +{ + rfc5549_setup_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server2}a ${net_tester_4}.40/31) \ + } rfc5549 \ + inet proto tcp \ + keep state" + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding rfc5549 support, + # we test for it. One of hosts is a network prefix. This gets iterated + # too. Finally the pool loops back. + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4202 + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4203 + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4204 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server2_4}.40@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4202 .* route-to: ${net_server2_4}.41@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4203 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4204 .* route-to: ${net_server2_4}.40@${epair_server2}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +rfc5549_mixed_af_subnet_cleanup() +{ + pft_cleanup +} + +atf_test_case "rfc5549_mixed_af_table_loop" "cleanup" + +rfc5549_mixed_af_table_loop_head() +{ + atf_set descr 'RFC5549 multiple gateways of mixed AF with a table' + atf_set require.user root +} + +rfc5549_mixed_af_table_loop_body() +{ + rfc5549_setup_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44/31 ${net_server2_6}::42:0/127 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server2}a ) \ + } rfc5549 \ + inet proto tcp \ + keep state" + + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding rfc5549 support, + # we test for it. One of hosts is a a table with IPv6 and IPv4 addresses + # This gets iterated, first IPv6 addresses, then IPv4. Finally the pool + # loops back. + for port in $(seq 1 9); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server2_6}::42:0@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4202 .* route-to: ${net_server2_6}::42:1@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4203 .* route-to: ${net_server2_4}.40@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4204 .* route-to: ${net_server2_4}.41@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4205 .* route-to: ${net_server2_4}.44@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4206 .* route-to: ${net_server2_4}.45@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4207 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4208 .* route-to: ${net_server2_6}::42:0@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4209 .* route-to: ${net_server2_6}::42:1@${epair_server2}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +rfc5549_mixed_af_table_loop_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4" @@ -943,4 +1257,9 @@ atf_add_test_case "sticky" atf_add_test_case "empty_pool" atf_add_test_case "table_loop" + atf_add_test_case "rfc5549_single_ipv4" + atf_add_test_case "rfc5549_single_ipv6" + atf_add_test_case "rfc5549_mixed_af" + atf_add_test_case "rfc5549_mixed_af_subnet" + atf_add_test_case "rfc5549_mixed_af_table_loop" } 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 @@ -503,6 +503,89 @@ pft_cleanup } +atf_test_case "mixed_af" "cleanup" +mixed_af_head() +{ + atf_set descr 'Test mixed address family source tracking' + atf_set require.user root +} + +mixed_af_body() +{ + setup_router_server_nat64 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_4_host_server}) \ + (${epair_server2}a ${net_server2_6_host_server}) \ + } rfc5549 sticky-address \ + inet6 proto tcp from any to 64:ff9b::/96 \ + af-to inet from ${net_clients_4}.0/${net_clients_4_mask} round-robin sticky-address" + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::1 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4201 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::1 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4202 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::2 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4203 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + nodes=$(mktemp) || exit 1 + jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes + + # States are checked for proper route-to information. + # The route-to gateway is IPv4. + # FIXME: Sticky-address is broken for af-to pools! + # The SN is created but apparently not used, as seen in states. + for state_regexp in \ + "${epair_tester}b tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \ + "${epair_tester}b tcp 203.0.113.1:4202 \(2001:db8:44::1\[4202\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \ + "${epair_tester}b tcp 203.0.113.2:4203 \(2001:db8:44::2\[4203\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # Source nodes map IPv6 source address onto IPv4 gateway and IPv4 SNAT address. + for node_regexp in \ + '2001:db8:44::2 -> 203.0.113.2 .* states 1, .* NAT/RDR sticky-address' \ + '2001:db8:44::2 -> 198.51.100.18 .* states 1, .* route sticky-address' \ + '2001:db8:44::1 -> 203.0.113.0 .* states 2, .* NAT/RDR sticky-address' \ + '2001:db8:44::1 -> 2001:db8:4202::2 .* states 2, .* route sticky-address' \ + ; do + grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + done +} + +mixed_af_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "source_track" @@ -512,4 +595,5 @@ atf_add_test_case "max_src_states_global" atf_add_test_case "sn_types_compat" atf_add_test_case "sn_types_pass" + atf_add_test_case "mixed_af" }