diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -923,6 +923,29 @@ u_int8_t pad[1]; }; +/* Keep synced with struct pf_udp_endpoint. */ +struct pf_udp_endpoint_cmp { + struct pf_addr addr; + uint16_t port; + sa_family_t af; + uint8_t pad[1]; +}; + +struct pf_udp_endpoint { + struct pf_addr addr; + uint16_t port; + sa_family_t af; + uint8_t pad[1]; + + struct pf_udp_mapping *mapping; + LIST_ENTRY(pf_udp_endpoint) entry; +}; + +struct pf_udp_mapping { + struct pf_udp_endpoint endpoints[2]; + u_int refs; +}; + /* Keep synced with struct pf_state_key. */ struct pf_state_key_cmp { struct pf_addr addr[2]; @@ -1052,6 +1075,7 @@ union pf_krule_ptr nat_rule; struct pf_addr rt_addr; struct pf_state_key *key[2]; /* addresses stack and wire */ + struct pf_udp_mapping *udp_mapping; struct pfi_kkif *kif; struct pfi_kkif *orig_kif; /* The real kif, even if we're a floating state (i.e. if == V_pfi_all). */ struct pfi_kkif *rt_kif; @@ -2071,6 +2095,11 @@ struct mtx lock; }; +struct pf_udpendpointhash { + LIST_HEAD(, pf_udp_endpoint) endpoints; + struct mtx lock; +}; + struct pf_keyhash { LIST_HEAD(, pf_state_key) keys; struct mtx lock; @@ -2086,8 +2115,10 @@ extern u_long pf_srchashmask; #define PF_HASHSIZ (131072) #define PF_SRCHASHSIZ (PF_HASHSIZ/4) +VNET_DECLARE(struct pf_udpendpointhash *, pf_udpendpointhash); VNET_DECLARE(struct pf_keyhash *, pf_keyhash); VNET_DECLARE(struct pf_idhash *, pf_idhash); +#define V_pf_udpendpointhash VNET(pf_udpendpointhash) #define V_pf_keyhash VNET(pf_keyhash) #define V_pf_idhash VNET(pf_idhash) VNET_DECLARE(struct pf_srchash *, pf_srchash); @@ -2162,6 +2193,8 @@ #define V_pf_state_z VNET(pf_state_z) VNET_DECLARE(uma_zone_t, pf_state_key_z); #define V_pf_state_key_z VNET(pf_state_key_z) +VNET_DECLARE(uma_zone_t, pf_udp_mapping_z); +#define V_pf_udp_mapping_z VNET(pf_udp_mapping_z) VNET_DECLARE(uma_zone_t, pf_state_scrub_z); #define V_pf_state_scrub_z VNET(pf_state_scrub_z) @@ -2213,6 +2246,13 @@ u_int, int *); extern bool pf_find_state_all_exists(struct pf_state_key_cmp *, u_int); +extern struct pf_udp_mapping *pf_udp_mapping_find(struct pf_udp_endpoint_cmp *endpoint); +extern struct pf_udp_mapping *pf_udp_mapping_create(sa_family_t af, + struct pf_addr *src_addr, uint16_t src_port, + struct pf_addr *nat_addr, uint16_t nat_port); +extern int pf_udp_mapping_insert(struct pf_udp_mapping *mapping); +extern void pf_udp_mapping_release(struct pf_udp_mapping *mapping); + extern struct pf_ksrc_node *pf_find_src_node(struct pf_addr *, struct pf_krule *, sa_family_t, struct pf_srchash **, bool); @@ -2480,7 +2520,8 @@ int, struct pfi_kkif *, struct pf_ksrc_node **, struct pf_state_key **, struct pf_state_key **, struct pf_addr *, struct pf_addr *, - uint16_t, uint16_t, struct pf_kanchor_stackframe *); + uint16_t, uint16_t, struct pf_kanchor_stackframe *, + struct pf_udp_mapping **udp_mapping); struct pf_state_key *pf_state_key_setup(struct pf_pdesc *, struct pf_addr *, struct pf_addr *, 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 @@ -248,6 +248,7 @@ uma_zone_t pf_mtag_z; VNET_DEFINE(uma_zone_t, pf_state_z); VNET_DEFINE(uma_zone_t, pf_state_key_z); +VNET_DEFINE(uma_zone_t, pf_udp_mapping_z); VNET_DEFINE(struct unrhdr64, pf_stateid); @@ -291,7 +292,7 @@ struct pf_state_key *, struct mbuf *, int, u_int16_t, u_int16_t, int *, struct pfi_kkif *, struct pf_kstate **, int, u_int16_t, u_int16_t, - int, struct pf_krule_slist *); + int, struct pf_krule_slist *, struct pf_udp_mapping *); static int pf_test_fragment(struct pf_krule **, struct pfi_kkif *, struct mbuf *, void *, struct pf_pdesc *, struct pf_krule **, struct pf_kruleset **); @@ -417,6 +418,7 @@ VNET_DEFINE(struct pf_keyhash *, pf_keyhash); VNET_DEFINE(struct pf_idhash *, pf_idhash); VNET_DEFINE(struct pf_srchash *, pf_srchash); +VNET_DEFINE(struct pf_udpendpointhash *, pf_udpendpointhash); SYSCTL_NODE(_net, OID_AUTO, pf, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "pf(4)"); @@ -600,6 +602,18 @@ return (h & pf_hashmask); } +static inline uint32_t +pf_hashudpendpoint(struct pf_udp_endpoint *endpoint) +{ + uint32_t h; + + h = murmur3_32_hash32((uint32_t *)endpoint, + sizeof(struct pf_udp_endpoint_cmp)/sizeof(uint32_t), + V_pf_hashseed); + + return (h & pf_hashmask); +} + static __inline uint32_t pf_hashsrc(struct pf_addr *addr, sa_family_t af) { @@ -1010,6 +1024,7 @@ { struct pf_keyhash *kh; struct pf_idhash *ih; + struct pf_udpendpointhash *uh; struct pf_srchash *sh; u_int i; @@ -1030,11 +1045,16 @@ V_pf_state_key_z = uma_zcreate("pf state keys", sizeof(struct pf_state_key), pf_state_key_ctor, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); + + V_pf_udp_mapping_z = uma_zcreate("pf UDP mappings", + sizeof(struct pf_udp_mapping), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); V_pf_keyhash = mallocarray(pf_hashsize, sizeof(struct pf_keyhash), M_PFHASH, M_NOWAIT | M_ZERO); V_pf_idhash = mallocarray(pf_hashsize, sizeof(struct pf_idhash), M_PFHASH, M_NOWAIT | M_ZERO); + V_pf_udpendpointhash = mallocarray(pf_hashsize, sizeof(struct pf_udpendpointhash), + M_PFHASH, M_WAITOK | M_ZERO); if (V_pf_keyhash == NULL || V_pf_idhash == NULL) { printf("pf: Unable to allocate memory for " "state_hashsize %lu.\n", pf_hashsize); @@ -1050,10 +1070,11 @@ } pf_hashmask = pf_hashsize - 1; - for (i = 0, kh = V_pf_keyhash, ih = V_pf_idhash; i <= pf_hashmask; - i++, kh++, ih++) { + for (i = 0, kh = V_pf_keyhash, ih = V_pf_idhash, uh=V_pf_udpendpointhash; i <= pf_hashmask; + i++, kh++, ih++, uh++) { mtx_init(&kh->lock, "pf_keyhash", NULL, MTX_DEF | MTX_DUPOK); mtx_init(&ih->lock, "pf_idhash", NULL, MTX_DEF); + mtx_init(&uh->lock, "pf_udpendpointhash", NULL, MTX_DEF | MTX_DUPOK); } /* Source nodes. */ @@ -1111,21 +1132,26 @@ { struct pf_keyhash *kh; struct pf_idhash *ih; + struct pf_udpendpointhash *uh; struct pf_srchash *sh; struct pf_send_entry *pfse, *next; u_int i; - for (i = 0, kh = V_pf_keyhash, ih = V_pf_idhash; i <= pf_hashmask; - i++, kh++, ih++) { + for (i = 0, kh = V_pf_keyhash, ih = V_pf_idhash, uh=V_pf_udpendpointhash; i <= pf_hashmask; + i++, kh++, ih++, uh++) { KASSERT(LIST_EMPTY(&kh->keys), ("%s: key hash not empty", __func__)); KASSERT(LIST_EMPTY(&ih->states), ("%s: id hash not empty", __func__)); + KASSERT(LIST_EMPTY(&uh->endpoints), ("%s: udpendpoint hash not empty", + __func__)); mtx_destroy(&kh->lock); mtx_destroy(&ih->lock); + mtx_destroy(&uh->lock); } free(V_pf_keyhash, M_PFHASH); free(V_pf_idhash, M_PFHASH); + free(V_pf_udpendpointhash, M_PFHASH); for (i = 0, sh = V_pf_srchash; i <= pf_srchashmask; i++, sh++) { KASSERT(LIST_EMPTY(&sh->nodes), @@ -1142,6 +1168,7 @@ uma_zdestroy(V_pf_sources_z); uma_zdestroy(V_pf_state_z); uma_zdestroy(V_pf_state_key_z); + uma_zdestroy(V_pf_udp_mapping_z); } static int @@ -1656,6 +1683,114 @@ return (false); } +struct pf_udp_mapping* +pf_udp_mapping_create(sa_family_t af, struct pf_addr *src_addr, uint16_t src_port, + struct pf_addr *nat_addr, uint16_t nat_port) +{ + struct pf_udp_mapping *mapping; + + mapping = uma_zalloc(V_pf_udp_mapping_z, M_NOWAIT | M_ZERO); + if (mapping == NULL) + return NULL; + PF_ACPY(&mapping->endpoints[0].addr, src_addr, af); + mapping->endpoints[0].port = src_port; + mapping->endpoints[0].af = af; + mapping->endpoints[0].mapping = mapping; + PF_ACPY(&mapping->endpoints[1].addr, nat_addr, af); + mapping->endpoints[1].port = nat_port; + mapping->endpoints[1].af = af; + mapping->endpoints[1].mapping = mapping; + refcount_init(&mapping->refs, 1); + return (mapping); +} + +int +pf_udp_mapping_insert(struct pf_udp_mapping *mapping) +{ + struct pf_udpendpointhash *h0, *h1; + struct pf_udp_endpoint *endpoint; + int ret = 1; + + h0 = &V_pf_udpendpointhash[pf_hashudpendpoint(&mapping->endpoints[0])]; + h1 = &V_pf_udpendpointhash[pf_hashudpendpoint(&mapping->endpoints[1])]; + if (h0 == h1) { + PF_HASHROW_LOCK(h0); + } else if (h0 < h1) { + PF_HASHROW_LOCK(h0); + PF_HASHROW_LOCK(h1); + } else { + PF_HASHROW_LOCK(h1); + PF_HASHROW_LOCK(h0); + } + + LIST_FOREACH(endpoint, &h0->endpoints, entry) + if (bcmp(endpoint, &mapping->endpoints[0], sizeof(struct pf_udp_endpoint_cmp)) == 0) + break; + if (endpoint != NULL) + goto cleanup; + LIST_FOREACH(endpoint, &h1->endpoints, entry) + if (bcmp(endpoint, &mapping->endpoints[1], sizeof(struct pf_udp_endpoint_cmp)) == 0) + break; + if (endpoint != NULL) + goto cleanup; + LIST_INSERT_HEAD(&h0->endpoints, &mapping->endpoints[0], entry); + LIST_INSERT_HEAD(&h1->endpoints, &mapping->endpoints[1], entry); + ret = 0; + +cleanup: + if (h0 != h1) { + PF_HASHROW_UNLOCK(h0); + PF_HASHROW_UNLOCK(h1); + } else { + PF_HASHROW_UNLOCK(h0); + } + return (ret); +} + +void +pf_udp_mapping_release(struct pf_udp_mapping *mapping) +{ + /* refcount is synchronized on the source endpoint's row lock */ + struct pf_udpendpointhash *h0, *h1; + + h0 = &V_pf_udpendpointhash[pf_hashudpendpoint(&mapping->endpoints[0])]; + PF_HASHROW_LOCK(h0); + if (refcount_release(&mapping->refs)) { + LIST_REMOVE(&mapping->endpoints[0], entry); + PF_HASHROW_UNLOCK(h0); + h1 = &V_pf_udpendpointhash[pf_hashudpendpoint(&mapping->endpoints[1])]; + PF_HASHROW_LOCK(h1); + LIST_REMOVE(&mapping->endpoints[1], entry); + PF_HASHROW_UNLOCK(h1); + + uma_zfree(V_pf_udp_mapping_z, mapping); + } else { + PF_HASHROW_UNLOCK(h0); + } +} + +struct pf_udp_mapping * +pf_udp_mapping_find(struct pf_udp_endpoint_cmp *key) +{ + struct pf_udpendpointhash *uh; + struct pf_udp_endpoint *endpoint; + + uh = &V_pf_udpendpointhash[pf_hashudpendpoint((struct pf_udp_endpoint*)key)]; + + PF_HASHROW_LOCK(uh); + LIST_FOREACH(endpoint, &uh->endpoints, entry) + if (bcmp(endpoint, key, sizeof(struct pf_udp_endpoint_cmp)) == 0 && + bcmp(endpoint, &endpoint->mapping->endpoints[0], sizeof(struct pf_udp_endpoint_cmp)) == 0) + break; + if (endpoint == NULL) { + PF_HASHROW_UNLOCK(uh); + return NULL; + } + refcount_acquire(&endpoint->mapping->refs); + PF_HASHROW_UNLOCK(uh); + return (endpoint->mapping); +} + /* END state table stuff */ static void @@ -2101,6 +2236,10 @@ PF_HASHROW_UNLOCK(ih); pf_detach_state(s); + + if (s->udp_mapping) + pf_udp_mapping_release(s->udp_mapping); + /* pf_state_insert() initialises refs to 2 */ return (pf_release_staten(s, 2)); } @@ -4360,6 +4499,7 @@ u_int16_t bproto_sum = 0, bip_sum = 0; u_int8_t icmptype = 0, icmpcode = 0; struct pf_kanchor_stackframe anchor_stack[PF_ANCHOR_STACKSIZE]; + struct pf_udp_mapping *udp_mapping = NULL; PF_RULES_RASSERT(); @@ -4428,7 +4568,7 @@ /* check packet for BINAT/NAT/RDR */ if ((nr = pf_get_translation(pd, m, off, kif, &nsn, &sk, - &nk, saddr, daddr, sport, dport, anchor_stack)) != NULL) { + &nk, saddr, daddr, sport, dport, anchor_stack, &udp_mapping)) != NULL) { KASSERT(sk != NULL, ("%s: null sk", __func__)); KASSERT(nk != NULL, ("%s: null nk", __func__)); @@ -4719,8 +4859,10 @@ int action; action = pf_create_state(r, nr, a, pd, nsn, nk, sk, m, off, sport, dport, &rewrite, kif, sm, tag, bproto_sum, bip_sum, - hdrlen, &match_rules); + hdrlen, &match_rules, udp_mapping); if (action != PF_PASS) { + if (udp_mapping != NULL) + pf_udp_mapping_release(udp_mapping); if (action == PF_DROP && (r->rule_flag & PFRULE_RETURN)) pf_return(r, nr, pd, sk, off, m, th, kif, @@ -4731,6 +4873,8 @@ } else { uma_zfree(V_pf_state_key_z, sk); uma_zfree(V_pf_state_key_z, nk); + if (udp_mapping != NULL) + pf_udp_mapping_release(udp_mapping); } /* copy back packet headers if we performed NAT operations */ @@ -4758,6 +4902,10 @@ uma_zfree(V_pf_state_key_z, sk); uma_zfree(V_pf_state_key_z, nk); + + if (udp_mapping != NULL) + pf_udp_mapping_release(udp_mapping); + return (PF_DROP); } @@ -4767,7 +4915,8 @@ struct pf_state_key *sk, struct mbuf *m, int off, u_int16_t sport, u_int16_t dport, int *rewrite, struct pfi_kkif *kif, struct pf_kstate **sm, int tag, u_int16_t bproto_sum, u_int16_t bip_sum, int hdrlen, - struct pf_krule_slist *match_rules) + struct pf_krule_slist *match_rules, + struct pf_udp_mapping *udp_mapping) { struct pf_kstate *s = NULL; struct pf_ksrc_node *sn = NULL; @@ -4993,6 +5142,7 @@ REASON_SET(&reason, PFRES_SYNPROXY); return (PF_SYNPROXY_DROP); } + s->udp_mapping = udp_mapping; return (PF_PASS); 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 @@ -64,7 +64,8 @@ uint16_t, int, struct pf_kanchor_stackframe *); static int pf_get_sport(sa_family_t, uint8_t, struct pf_krule *, struct pf_addr *, uint16_t, struct pf_addr *, uint16_t, struct pf_addr *, - uint16_t *, uint16_t, uint16_t, struct pf_ksrc_node **); + uint16_t *, uint16_t, uint16_t, struct pf_ksrc_node **, + struct pf_udp_mapping**); #define mix(a,b,c) \ do { \ @@ -218,14 +219,38 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r, struct pf_addr *saddr, uint16_t sport, struct pf_addr *daddr, uint16_t dport, struct pf_addr *naddr, uint16_t *nport, uint16_t low, - uint16_t high, struct pf_ksrc_node **sn) + uint16_t high, struct pf_ksrc_node **sn, struct pf_udp_mapping **udp_mapping) { struct pf_state_key_cmp key; struct pf_addr init_addr; + struct pf_srchash *sh = NULL; + + if (proto == IPPROTO_UDP) { + struct pf_udp_endpoint_cmp udp_source; + + bzero(&udp_source, sizeof(udp_source)); + udp_source.af = af; + PF_ACPY(&udp_source.addr, saddr, af); + udp_source.port = sport; + *udp_mapping = pf_udp_mapping_find(&udp_source); + if (*udp_mapping) { + PF_ACPY(naddr, &(*udp_mapping)->endpoints[1].addr, af); + *nport = (*udp_mapping)->endpoints[1].port; + /* as per pf_map_addr(): */ + if (*sn == NULL && r->rpool.opts & PF_POOL_STICKYADDR && + (r->rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) + *sn = pf_find_src_node(saddr, r, af, &sh, 0); + return (0); + } else { + *udp_mapping = pf_udp_mapping_create(af, saddr, sport, &init_addr, 0); + if (*udp_mapping == NULL) + return (1); + } + } bzero(&init_addr, sizeof(init_addr)); if (pf_map_addr(af, r, saddr, naddr, &init_addr, sn)) - return (1); + goto failed; bzero(&key, sizeof(key)); key.af = af; @@ -235,6 +260,8 @@ do { PF_ACPY(&key.addr[1], naddr, key.af); + if ((*udp_mapping)) + PF_ACPY(&(*udp_mapping)->endpoints[1].addr, naddr, af); /* * port search; start random, step; @@ -262,8 +289,16 @@ } else if (low == high) { key.port[1] = htons(low); if (!pf_find_state_all_exists(&key, PF_IN)) { - *nport = htons(low); - return (0); + if (proto == IPPROTO_UDP) { + (*udp_mapping)->endpoints[1].port = htons(low); + if (pf_udp_mapping_insert(*udp_mapping) == 0) { + *nport = htons(low); + return (0); + } + } else { + *nport = htons(low); + return (0); + } } } else { uint32_t tmp; @@ -278,18 +313,34 @@ cut = arc4random() % (1 + high - low) + low; /* low <= cut <= high */ for (tmp = cut; tmp <= high && tmp <= 0xffff; ++tmp) { - key.port[1] = htons(tmp); - if (!pf_find_state_all_exists(&key, PF_IN)) { - *nport = htons(tmp); - return (0); + if (proto == IPPROTO_UDP) { + (*udp_mapping)->endpoints[1].port = htons(tmp); + if (pf_udp_mapping_insert(*udp_mapping) == 0) { + *nport = htons(tmp); + return (0); + } + } else { + key.port[1] = htons(tmp); + if (!pf_find_state_all_exists(&key, PF_IN)) { + *nport = htons(tmp); + return (0); + } } } tmp = cut; for (tmp -= 1; tmp >= low && tmp <= 0xffff; --tmp) { + if (proto == IPPROTO_UDP) { + (*udp_mapping)->endpoints[1].port = htons(tmp); + if (pf_udp_mapping_insert(*udp_mapping) == 0) { + *nport = htons(tmp); + return (0); + } + } else { key.port[1] = htons(tmp); - if (!pf_find_state_all_exists(&key, PF_IN)) { - *nport = htons(tmp); - return (0); + if (!pf_find_state_all_exists(&key, PF_IN)) { + *nport = htons(tmp); + return (0); + } } } } @@ -311,14 +362,20 @@ return (1); } } while (! PF_AEQ(&init_addr, naddr, af) ); - return (1); /* none available */ + /* none available */ +failed: + if (*udp_mapping) { + uma_zfree(V_pf_udp_mapping_z, *udp_mapping); + *udp_mapping = NULL; + } + return (1); } static int pf_get_mape_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r, struct pf_addr *saddr, uint16_t sport, struct pf_addr *daddr, uint16_t dport, struct pf_addr *naddr, uint16_t *nport, - struct pf_ksrc_node **sn) + struct pf_ksrc_node **sn, struct pf_udp_mapping** udp_mapping) { uint16_t psmask, low, highmask; uint16_t i, ahigh, cut; @@ -338,13 +395,13 @@ for (i = cut; i <= ahigh; i++) { low = (i << ashift) | psmask; if (!pf_get_sport(af, proto, r, saddr, sport, daddr, dport, - naddr, nport, low, low | highmask, sn)) + naddr, nport, low, low | highmask, sn, udp_mapping)) return (0); } for (i = cut - 1; i > 0; i--) { low = (i << ashift) | psmask; if (!pf_get_sport(af, proto, r, saddr, sport, daddr, dport, - naddr, nport, low, low | highmask, sn)) + naddr, nport, low, low | highmask, sn, udp_mapping)) return (0); } return (1); @@ -567,7 +624,8 @@ struct pfi_kkif *kif, struct pf_ksrc_node **sn, struct pf_state_key **skp, struct pf_state_key **nkp, struct pf_addr *saddr, struct pf_addr *daddr, - uint16_t sport, uint16_t dport, struct pf_kanchor_stackframe *anchor_stack) + uint16_t sport, uint16_t dport, struct pf_kanchor_stackframe *anchor_stack, + struct pf_udp_mapping **udp_mapping) { struct pf_krule *r = NULL; struct pf_addr *naddr; @@ -629,7 +687,7 @@ } if (r->rpool.mape.offset > 0) { if (pf_get_mape_sport(pd->af, pd->proto, r, saddr, - sport, daddr, dport, naddr, nport, sn)) { + sport, daddr, dport, naddr, nport, sn, udp_mapping)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: MAP-E port allocation (%u/%u/%u)" " failed\n", @@ -639,7 +697,7 @@ goto notrans; } } else if (pf_get_sport(pd->af, pd->proto, r, saddr, sport, - daddr, dport, naddr, nport, low, high, sn)) { + daddr, dport, naddr, nport, low, high, sn, udp_mapping)) { DPFPRINTF(PF_DEBUG_MISC, ("pf: NAT proxy port allocation (%u-%u) failed\n", r->rpool.proxy_port[0], r->rpool.proxy_port[1])); 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 @@ -119,8 +119,93 @@ pft_cleanup } +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() +{ + pft_init + + epair_client=$(vnet_mkepair) + epair_nat=$(vnet_mkepair) + epair_server1=$(vnet_mkepair) + epair_server2=$(vnet_mkepair) + bridge=$(vnet_mkbridge) + + vnet_mkjail nat ${epair_client}b ${epair_nat}a + vnet_mkjail client ${epair_client}a + vnet_mkjail server1 ${epair_server1}a + vnet_mkjail server2 ${epair_server2}a + + ifconfig ${epair_server1}b up + ifconfig ${epair_server2}b up + ifconfig ${epair_nat}b up + ifconfig ${bridge} \ + addm ${epair_server1}b \ + addm ${epair_server2}b \ + addm ${epair_nat}b \ + up + + jexec nat ifconfig ${epair_client}b 192.0.2.1/24 up + jexec nat ifconfig ${epair_nat}a 10.42.42.42/8 up + jexec nat sysctl net.inet.ip.forwarding=1 + + jexec client ifconfig ${epair_client}a 192.0.2.2/24 up + jexec client route add default 192.0.2.1 + + jexec server1 ifconfig ${epair_server1}a 10.32.32.32/8 up + jexec server2 ifconfig ${epair_server2}a 10.22.22.22/8 up + + # Enable pf! + jexec nat pfctl -e + pft_set_rules nat \ + "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" + + jexec server1 nc -u -l 1234 -v 2> server1.out & + server1pid="$!" + jexec server2 nc -u -l 1234 -v 2> server2.out & + server2pid="$!" + + # send out three packets because sometimes one fails to go through + for i in $(seq 1 3); do + echo "ping" | jexec client nc -u 10.32.32.32 1234 -p 4242 -w 0 + echo "ping" | jexec client nc -u 10.22.22.22 1234 -p 4242 -w 0 + done + + ipport_server1=$(cat server1.out | grep Connection) + ipport_server2=$(cat server2.out | grep Connection) + + if [ -z "$ipport_server1" ]; then + atf_fail server1 did not receive connection from client + fi + + if [ -z "$ipport_server2" ]; then + atf_fail server2 did not receive connection from client + fi + + if [ ! "$ipport_server1" = "$ipport_server2" ]; then + echo "server1: $ipport_server1" + echo "server2: $ipport_server2" + atf_fail Received different IP:port on server1 than server2 + fi + kill $server1pid + kill $server2pid +} + +endpoint_independent_cleanup() +{ + pft_cleanup + rm -f server1.out + rm -f server2.out +} + atf_init_test_cases() { atf_add_test_case "exhaust" atf_add_test_case "nested_anchor" + atf_add_test_case "endpoint_independent" }