Edit

IABSD.fr/src/sys/net/pf_lb.c

Branch :

  • Show log

    Commit

  • Author : sashan
    Date : 2023-05-10 22:42:51
    Hash : e8d81675
    Message : nat-to may fail to insert state due to conflict on chosen source port number. This is typically indicated by 'wire key attach failed on...' message when pf(4) debugging is enabled. The problem is caused by glitch in pf_get_sport() which fails to discover conflict in advance. In order to fix it we must also calculate toeplitz hash in pf_get_sport() to initialize look up key properly. the bug has been kindly reported by joosepm _von_ gmail _dot_ com OK dlg@

  • sys/net/pf_lb.c
  • /*	$OpenBSD: pf_lb.c,v 1.74 2023/05/10 22:42:51 sashan Exp $ */
    
    /*
     * Copyright (c) 2001 Daniel Hartmeier
     * Copyright (c) 2002 - 2008 Henning Brauer
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     *    - Redistributions of source code must retain the above copyright
     *      notice, this list of conditions and the following disclaimer.
     *    - 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 COPYRIGHT HOLDERS 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
     * COPYRIGHT HOLDERS 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.
     *
     * Effort sponsored in part by the Defense Advanced Research Projects
     * Agency (DARPA) and Air Force Research Laboratory, Air Force
     * Materiel Command, USAF, under agreement number F30602-01-2-0537.
     *
     */
    
    #include "bpfilter.h"
    #include "pflog.h"
    #include "pfsync.h"
    #include "pflow.h"
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/mbuf.h>
    #include <sys/filio.h>
    #include <sys/socket.h>
    #include <sys/socketvar.h>
    #include <sys/kernel.h>
    #include <sys/time.h>
    #include <sys/pool.h>
    #include <sys/rwlock.h>
    #include <sys/syslog.h>
    #include <sys/stdint.h>
    
    #include <crypto/siphash.h>
    
    #include <net/if.h>
    #include <net/bpf.h>
    #include <net/route.h>
    
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/in_pcb.h>
    #include <netinet/ip_var.h>
    #include <netinet/ip_icmp.h>
    #include <netinet/icmp_var.h>
    #include <netinet/tcp.h>
    #include <netinet/tcp_seq.h>
    #include <netinet/tcp_timer.h>
    #include <netinet/udp.h>
    #include <netinet/udp_var.h>
    #include <netinet/if_ether.h>
    
    #ifdef INET6
    #include <netinet/ip6.h>
    #include <netinet/icmp6.h>
    #endif /* INET6 */
    
    #include <net/pfvar.h>
    #include <net/pfvar_priv.h>
    
    #if NPFLOG > 0
    #include <net/if_pflog.h>
    #endif	/* NPFLOG > 0 */
    
    #if NPFLOW > 0
    #include <net/if_pflow.h>
    #endif	/* NPFLOW > 0 */
    
    #if NPFSYNC > 0
    #include <net/if_pfsync.h>
    #endif /* NPFSYNC > 0 */
    
    u_int64_t		 pf_hash(struct pf_addr *, struct pf_addr *,
    			    struct pf_poolhashkey *, sa_family_t);
    int			 pf_get_sport(struct pf_pdesc *, struct pf_rule *,
    			    struct pf_addr *, u_int16_t *, u_int16_t,
    			    u_int16_t, struct pf_src_node **);
    int			 pf_map_addr_states_increase(sa_family_t,
    				struct pf_pool *, struct pf_addr *);
    int			 pf_get_transaddr_af(struct pf_rule *,
    			    struct pf_pdesc *, struct pf_src_node **);
    int			 pf_map_addr_sticky(sa_family_t, struct pf_rule *,
    			    struct pf_addr *, struct pf_addr *,
    			    struct pf_src_node **, struct pf_pool *,
    			    enum pf_sn_types);
    
    u_int64_t
    pf_hash(struct pf_addr *inaddr, struct pf_addr *hash,
        struct pf_poolhashkey *key, sa_family_t af)
    {
    	uint64_t res = 0;
    #ifdef INET6
    	union {
    		uint64_t hash64;
    		uint32_t hash32[2];
    	} h;
    #endif	/* INET6 */
    
    	switch (af) {
    	case AF_INET:
    		res = SipHash24((SIPHASH_KEY *)key,
    		    &inaddr->addr32[0], sizeof(inaddr->addr32[0]));
    		hash->addr32[0] = res;
    		break;
    #ifdef INET6
    	case AF_INET6:
    		res = SipHash24((SIPHASH_KEY *)key, &inaddr->addr32[0],
    		    4 * sizeof(inaddr->addr32[0]));
    		h.hash64 = res;
    		hash->addr32[0] = h.hash32[0];
    		hash->addr32[1] = h.hash32[1];
    		/*
    		 * siphash isn't big enough, but flipping it around is
    		 * good enough here.
    		 */
    		hash->addr32[2] = ~h.hash32[1];
    		hash->addr32[3] = ~h.hash32[0];
    		break;
    #endif /* INET6 */
    	default:
    		unhandled_af(af);
    	}
    	return (res);
    }
    
    int
    pf_get_sport(struct pf_pdesc *pd, struct pf_rule *r,
        struct pf_addr *naddr, u_int16_t *nport, u_int16_t low, u_int16_t high,
        struct pf_src_node **sn)
    {
    	struct pf_state_key_cmp	key;
    	struct pf_addr		init_addr;
    	u_int16_t		cut;
    	int			dir = (pd->dir == PF_IN) ? PF_OUT : PF_IN;
    	int			sidx = pd->sidx;
    	int			didx = pd->didx;
    
    	memset(&init_addr, 0, sizeof(init_addr));
    	if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr, &init_addr, sn, &r->nat,
    	    PF_SN_NAT))
    		return (1);
    
    	if (pd->proto == IPPROTO_ICMP) {
    		if (pd->ndport == htons(ICMP_ECHO)) {
    			low = 1;
    			high = 65535;
    		} else
    			return (0);	/* Don't try to modify non-echo ICMP */
    	}
    #ifdef INET6
    	if (pd->proto == IPPROTO_ICMPV6) {
    		if (pd->ndport == htons(ICMP6_ECHO_REQUEST)) {
    			low = 1;
    			high = 65535;
    		} else
    			return (0);	/* Don't try to modify non-echo ICMP */
    	}
    #endif /* INET6 */
    
    	do {
    		key.af = pd->naf;
    		key.proto = pd->proto;
    		key.rdomain = pd->rdomain;
    		pf_addrcpy(&key.addr[didx], &pd->ndaddr, key.af);
    		pf_addrcpy(&key.addr[sidx], naddr, key.af);
    		key.port[didx] = pd->ndport;
    
    		/*
    		 * port search; start random, step;
    		 * similar 2 portloop in in_pcbbind
    		 */
    		if (!(pd->proto == IPPROTO_TCP || pd->proto == IPPROTO_UDP ||
    		    pd->proto == IPPROTO_ICMP || pd->proto == IPPROTO_ICMPV6)) {
    			/* XXX bug: icmp states dont use the id on both
    			 * XXX sides (traceroute -I through nat) */
    			key.port[sidx] = pd->nsport;
    			key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
    			    &key.addr[1], key.port[0], key.port[1]);
    			if (pf_find_state_all(&key, dir, NULL) == NULL) {
    				*nport = pd->nsport;
    				return (0);
    			}
    		} else if (low == 0 && high == 0) {
    			key.port[sidx] = pd->nsport;
    			key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
    			    &key.addr[1], key.port[0], key.port[1]);
    			if (pf_find_state_all(&key, dir, NULL) == NULL) {
    				*nport = pd->nsport;
    				return (0);
    			}
    		} else if (low == high) {
    			key.port[sidx] = htons(low);
    			key.hash = pf_pkt_hash(key.af, key.proto, &key.addr[0],
    			    &key.addr[1], key.port[0], key.port[1]);
    			if (pf_find_state_all(&key, dir, NULL) == NULL) {
    				*nport = htons(low);
    				return (0);
    			}
    		} else {
    			u_int32_t tmp;
    
    			if (low > high) {
    				tmp = low;
    				low = high;
    				high = tmp;
    			}
    			/* low < high */
    			cut = arc4random_uniform(1 + high - low) + low;
    			/* low <= cut <= high */
    			for (tmp = cut; tmp <= high && tmp <= 0xffff; ++tmp) {
    				key.port[sidx] = htons(tmp);
    				key.hash = pf_pkt_hash(key.af, key.proto,
    				    &key.addr[0], &key.addr[1], key.port[0],
    				    key.port[1]);
    				if (pf_find_state_all(&key, dir, NULL) ==
    				    NULL && !in_baddynamic(tmp, pd->proto)) {
    					*nport = htons(tmp);
    					return (0);
    				}
    			}
    			tmp = cut;
    			for (tmp -= 1; tmp >= low && tmp <= 0xffff; --tmp) {
    				key.port[sidx] = htons(tmp);
    				key.hash = pf_pkt_hash(key.af, key.proto,
    				    &key.addr[0], &key.addr[1], key.port[0],
    				    key.port[1]);
    				if (pf_find_state_all(&key, dir, NULL) ==
    				    NULL && !in_baddynamic(tmp, pd->proto)) {
    					*nport = htons(tmp);
    					return (0);
    				}
    			}
    		}
    
    		switch (r->nat.opts & PF_POOL_TYPEMASK) {
    		case PF_POOL_RANDOM:
    		case PF_POOL_ROUNDROBIN:
    		case PF_POOL_LEASTSTATES:
    			/*
    			 * pick a different source address since we're out
    			 * of free port choices for the current one.
    			 */
    			if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr,
    			    &init_addr, sn, &r->nat, PF_SN_NAT))
    				return (1);
    			break;
    		case PF_POOL_NONE:
    		case PF_POOL_SRCHASH:
    		case PF_POOL_BITMASK:
    		default:
    			return (1);
    		}
    	} while (! PF_AEQ(&init_addr, naddr, pd->naf) );
    	return (1);					/* none available */
    }
    
    int
    pf_map_addr_sticky(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,
        struct pf_addr *naddr, struct pf_src_node **sns, struct pf_pool *rpool,
        enum pf_sn_types type)
    {
    	struct pf_addr		*raddr, *rmask, *cached;
    	struct pf_state		*s;
    	struct pf_src_node	 k;
    	int			 valid;
    
    	k.af = af;
    	k.type = type;
    	pf_addrcpy(&k.addr, saddr, af);
    	k.rule.ptr = r;
    	pf_status.scounters[SCNT_SRC_NODE_SEARCH]++;
    	sns[type] = RB_FIND(pf_src_tree, &tree_src_tracking, &k);
    	if (sns[type] == NULL)
    		return (-1);
    
    	/* check if the cached entry is still valid */
    	cached = &(sns[type])->raddr;
    	valid = 0;
    	if (PF_AZERO(cached, af)) {
    		valid = 1;
    	} else if (rpool->addr.type == PF_ADDR_DYNIFTL) {
    		if (pfr_kentry_byaddr(rpool->addr.p.dyn->pfid_kt, cached,
    		    af, 0))
    			valid = 1;
    	} else if (rpool->addr.type == PF_ADDR_TABLE) {
    		if (pfr_kentry_byaddr(rpool->addr.p.tbl, cached, af, 0))
    			valid = 1;
    	} else if (rpool->addr.type != PF_ADDR_NOROUTE) {
    		raddr = &rpool->addr.v.a.addr;
    		rmask = &rpool->addr.v.a.mask;
    		valid = pf_match_addr(0, raddr, rmask, cached, af);
    	}
    	if (!valid) {
    		if (pf_status.debug >= LOG_DEBUG) {
    			log(LOG_DEBUG, "pf: pf_map_addr: "
    			    "stale src tracking (%u) ", type);
    			pf_print_host(&k.addr, 0, af);
    			addlog(" to ");
    			pf_print_host(cached, 0, af);
    			addlog("\n");
    		}
    		if (sns[type]->states != 0) {
    			/* XXX expensive */
    			RBT_FOREACH(s, pf_state_tree_id, &tree_id)
    				pf_state_rm_src_node(s, sns[type]);
    		}
    		sns[type]->expire = 1;
    		pf_remove_src_node(sns[type]);
    		sns[type] = NULL;
    		return (-1);
    	}
    
    
    	if (!PF_AZERO(cached, af)) {
    		pf_addrcpy(naddr, cached, af);
    		if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_LEASTSTATES &&
    		    pf_map_addr_states_increase(af, rpool, cached) == -1)
    			return (-1);
    	}
    	if (pf_status.debug >= LOG_DEBUG) {
    		log(LOG_DEBUG, "pf: pf_map_addr: "
    		    "src tracking (%u) maps ", type);
    		pf_print_host(&k.addr, 0, af);
    		addlog(" to ");
    		pf_print_host(naddr, 0, af);
    		addlog("\n");
    	}
    
    	if (sns[type]->kif != NULL)
    		rpool->kif = sns[type]->kif;
    
    	return (0);
    }
    
    uint32_t
    pf_rand_addr(uint32_t mask)
    {
    	uint32_t addr;
    
    	mask = ~ntohl(mask);
    	addr = arc4random_uniform(mask + 1);
    
    	return (htonl(addr));
    }
    
    int
    pf_map_addr(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,
        struct pf_addr *naddr, struct pf_addr *init_addr, struct pf_src_node **sns,
        struct pf_pool *rpool, enum pf_sn_types type)
    {
    	struct pf_addr		 hash;
    	struct pf_addr		 faddr;
    	struct pf_addr		*raddr = &rpool->addr.v.a.addr;
    	struct pf_addr		*rmask = &rpool->addr.v.a.mask;
    	struct pfr_ktable	*kt;
    	struct pfi_kif		*kif;
    	u_int64_t		 states;
    	u_int16_t		 weight;
    	u_int64_t		 load;
    	u_int64_t		 cload;
    	u_int64_t		 hashidx;
    	int			 cnt;
    
    	if (sns[type] == NULL && rpool->opts & PF_POOL_STICKYADDR &&
    	    (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE &&
    	    pf_map_addr_sticky(af, r, saddr, naddr, sns, rpool, type) == 0)
    		return (0);
    
    	if (rpool->addr.type == PF_ADDR_NOROUTE)
    		return (1);
    	if (rpool->addr.type == PF_ADDR_DYNIFTL) {
    		switch (af) {
    		case AF_INET:
    			if (rpool->addr.p.dyn->pfid_acnt4 < 1 &&
    			    !PF_POOL_DYNTYPE(rpool->opts))
    				return (1);
    			raddr = &rpool->addr.p.dyn->pfid_addr4;
    			rmask = &rpool->addr.p.dyn->pfid_mask4;
    			break;
    #ifdef INET6
    		case AF_INET6:
    			if (rpool->addr.p.dyn->pfid_acnt6 < 1 &&
    			    !PF_POOL_DYNTYPE(rpool->opts))
    				return (1);
    			raddr = &rpool->addr.p.dyn->pfid_addr6;
    			rmask = &rpool->addr.p.dyn->pfid_mask6;
    			break;
    #endif /* INET6 */
    		default:
    			unhandled_af(af);
    		}
    	} else if (rpool->addr.type == PF_ADDR_TABLE) {
    		if (!PF_POOL_DYNTYPE(rpool->opts))
    			return (1); /* unsupported */
    	} else {
    		raddr = &rpool->addr.v.a.addr;
    		rmask = &rpool->addr.v.a.mask;
    	}
    
    	switch (rpool->opts & PF_POOL_TYPEMASK) {
    	case PF_POOL_NONE:
    		pf_addrcpy(naddr, raddr, af);
    		break;
    	case PF_POOL_BITMASK:
    		pf_poolmask(naddr, raddr, rmask, saddr, af);
    		break;
    	case PF_POOL_RANDOM:
    		if (rpool->addr.type == PF_ADDR_TABLE ||
    		    rpool->addr.type == PF_ADDR_DYNIFTL) {
    			if (rpool->addr.type == PF_ADDR_TABLE)
    				kt = rpool->addr.p.tbl;
    			else
    				kt = rpool->addr.p.dyn->pfid_kt;
    			kt = pfr_ktable_select_active(kt);
    			if (kt == NULL)
    				return (1);
    
    			cnt = kt->pfrkt_cnt;
    			if (cnt == 0)
    				rpool->tblidx = 0;
    			else
    				rpool->tblidx = (int)arc4random_uniform(cnt);
    			memset(&rpool->counter, 0, sizeof(rpool->counter));
    			if (pfr_pool_get(rpool, &raddr, &rmask, af))
    				return (1);
    			pf_addrcpy(naddr, &rpool->counter, af);
    		} else if (init_addr != NULL && PF_AZERO(init_addr, af)) {
    			switch (af) {
    			case AF_INET:
    				rpool->counter.addr32[0] = pf_rand_addr(
    				    rmask->addr32[0]);
    				break;
    #ifdef INET6
    			case AF_INET6:
    				if (rmask->addr32[3] != 0xffffffff)
    					rpool->counter.addr32[3] = pf_rand_addr(
    					    rmask->addr32[3]);
    				else
    					break;
    				if (rmask->addr32[2] != 0xffffffff)
    					rpool->counter.addr32[2] = pf_rand_addr(
    					    rmask->addr32[2]);
    				else
    					break;
    				if (rmask->addr32[1] != 0xffffffff)
    					rpool->counter.addr32[1] = pf_rand_addr(
    					    rmask->addr32[1]);
    				else
    					break;
    				if (rmask->addr32[0] != 0xffffffff)
    					rpool->counter.addr32[0] = pf_rand_addr(
    					    rmask->addr32[0]);
    				break;
    #endif /* INET6 */
    			default:
    				unhandled_af(af);
    			}
    			pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
    			pf_addrcpy(init_addr, naddr, af);
    
    		} else {
    			pf_addr_inc(&rpool->counter, af);
    			pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
    		}
    		break;
    	case PF_POOL_SRCHASH:
    		hashidx = pf_hash(saddr, &hash, &rpool->key, af);
    
    		if (rpool->addr.type == PF_ADDR_TABLE ||
    		    rpool->addr.type == PF_ADDR_DYNIFTL) {
    			if (rpool->addr.type == PF_ADDR_TABLE)
    				kt = rpool->addr.p.tbl;
    			else
    				kt = rpool->addr.p.dyn->pfid_kt;
    			kt = pfr_ktable_select_active(kt);
    			if (kt == NULL)
    				return (1);
    
    			cnt = kt->pfrkt_cnt;
    			if (cnt == 0)
    				rpool->tblidx = 0;
    			else
    				rpool->tblidx = (int)(hashidx % cnt);
    			memset(&rpool->counter, 0, sizeof(rpool->counter));
    			if (pfr_pool_get(rpool, &raddr, &rmask, af))
    				return (1);
    			pf_addrcpy(naddr, &rpool->counter, af);
    		} else {
    			pf_poolmask(naddr, raddr, rmask, &hash, af);
    		}
    		break;
    	case PF_POOL_ROUNDROBIN:
    		if (rpool->addr.type == PF_ADDR_TABLE ||
    		    rpool->addr.type == PF_ADDR_DYNIFTL) {
    			if (pfr_pool_get(rpool, &raddr, &rmask, af)) {
    				/*
    				 * reset counter in case its value
    				 * has been removed from the pool.
    				 */
    				memset(&rpool->counter, 0,
    				    sizeof(rpool->counter));
    				if (pfr_pool_get(rpool, &raddr, &rmask, af))
    					return (1);
    			}
    		} else if (PF_AZERO(&rpool->counter, af)) {
    			/*
    			 * fall back to POOL_NONE if there is a single host
    			 * address in pool.
    			 */
    			if (af == AF_INET &&
    			    rmask->addr32[0] == INADDR_BROADCAST) {
    				pf_addrcpy(naddr, raddr, af);
    				break;
    			}
    #ifdef INET6
    			if (af == AF_INET6 &&
    			    IN6_ARE_ADDR_EQUAL(&rmask->v6, &in6mask128)) {
    				pf_addrcpy(naddr, raddr, af);
    				break;
    			}
    #endif
    		} else if (pf_match_addr(0, raddr, rmask, &rpool->counter, af))
    			return (1);
    
    		/* iterate over table if it contains entries which are weighted */
    		if ((rpool->addr.type == PF_ADDR_TABLE &&
    		    rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
    		    (rpool->addr.type == PF_ADDR_DYNIFTL &&
    		    rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0)) {
    			do {
    				if (rpool->addr.type == PF_ADDR_TABLE ||
    				    rpool->addr.type == PF_ADDR_DYNIFTL) {
    					if (pfr_pool_get(rpool,
    					    &raddr, &rmask, af))
    						return (1);
    				} else {
    					log(LOG_ERR, "pf: pf_map_addr: "
    					    "weighted RR failure");
    					return (1);
    				}
    				if (rpool->weight >= rpool->curweight)
    					break;
    				pf_addr_inc(&rpool->counter, af);
    			} while (1);
     
    			weight = rpool->weight;
    		}
    
    		pf_poolmask(naddr, raddr, rmask, &rpool->counter, af);
    		if (init_addr != NULL && PF_AZERO(init_addr, af))
    			pf_addrcpy(init_addr, &rpool->counter, af);
    		pf_addr_inc(&rpool->counter, af);
    		break;
    	case PF_POOL_LEASTSTATES:
    		/* retrieve an address first */
    		if (rpool->addr.type == PF_ADDR_TABLE ||
    		    rpool->addr.type == PF_ADDR_DYNIFTL) {
    			if (pfr_pool_get(rpool, &raddr, &rmask, af)) {
    				/* see PF_POOL_ROUNDROBIN */
    				memset(&rpool->counter, 0,
    				    sizeof(rpool->counter));
    				if (pfr_pool_get(rpool, &raddr, &rmask, af))
    					return (1);
    			}
    		} else if (pf_match_addr(0, raddr, rmask, &rpool->counter, af))
    			return (1);
    
    		states = rpool->states;
    		weight = rpool->weight;
    		kif = rpool->kif;
    
    		if ((rpool->addr.type == PF_ADDR_TABLE &&
    		    rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
    		    (rpool->addr.type == PF_ADDR_DYNIFTL &&
    		    rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
    			load = ((UINT16_MAX * rpool->states) / rpool->weight);
    		else
    			load = states;
    
    		pf_addrcpy(&faddr, &rpool->counter, af);
    
    		pf_addrcpy(naddr, &rpool->counter, af);
    		if (init_addr != NULL && PF_AZERO(init_addr, af))
    			pf_addrcpy(init_addr, naddr, af);
    
    		/*
    		 * iterate *once* over whole table and find destination with
    		 * least connection
    		 */
    		do  {
    			pf_addr_inc(&rpool->counter, af);
    			if (rpool->addr.type == PF_ADDR_TABLE ||
    			    rpool->addr.type == PF_ADDR_DYNIFTL) {
    				if (pfr_pool_get(rpool, &raddr, &rmask, af))
    					return (1);
    			} else if (pf_match_addr(0, raddr, rmask,
    			    &rpool->counter, af))
    				return (1);
    
    			if ((rpool->addr.type == PF_ADDR_TABLE &&
    			    rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
    			    (rpool->addr.type == PF_ADDR_DYNIFTL &&
    			    rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
    				cload = ((UINT16_MAX * rpool->states)
    					/ rpool->weight);
    			else
    				cload = rpool->states;
    
    			/* find lc minimum */
    			if (cload < load) {
    				states = rpool->states;
    				weight = rpool->weight;
    				kif = rpool->kif;
    				load = cload;
    
    				pf_addrcpy(naddr, &rpool->counter, af);
    				if (init_addr != NULL &&
    				    PF_AZERO(init_addr, af))
    				    pf_addrcpy(init_addr, naddr, af);
    			}
    		} while (pf_match_addr(1, &faddr, rmask, &rpool->counter, af) &&
    		    (states > 0));
    
    		if (pf_map_addr_states_increase(af, rpool, naddr) == -1)
    			return (1);
    		/* revert the kif which was set by pfr_pool_get() */
    		rpool->kif = kif;
    		break;
    	}
    
    	if (rpool->opts & PF_POOL_STICKYADDR) {
    		if (sns[type] != NULL) {
    			pf_remove_src_node(sns[type]);
    			sns[type] = NULL;
    		}
    		if (pf_insert_src_node(&sns[type], r, type, af, saddr, naddr,
    		    rpool->kif))
    			return (1);
    	}
    
    	if (pf_status.debug >= LOG_INFO &&
    	    (rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_NONE) {
    		log(LOG_INFO, "pf: pf_map_addr: selected address ");
    		pf_print_host(naddr, 0, af);
    		if ((rpool->opts & PF_POOL_TYPEMASK) ==
    		    PF_POOL_LEASTSTATES)
    			addlog(" with state count %llu", states);
    		if ((rpool->addr.type == PF_ADDR_TABLE &&
    		    rpool->addr.p.tbl->pfrkt_refcntcost > 0) ||
    		    (rpool->addr.type == PF_ADDR_DYNIFTL &&
    		    rpool->addr.p.dyn->pfid_kt->pfrkt_refcntcost > 0))
    			addlog(" with weight %u", weight);
    		addlog("\n");
    	}
    
    	return (0);
    }
    
    int
    pf_map_addr_states_increase(sa_family_t af, struct pf_pool *rpool,
        struct pf_addr *naddr)
    {
    	if (rpool->addr.type == PF_ADDR_TABLE) {
    		if (pfr_states_increase(rpool->addr.p.tbl,
    		    naddr, af) == -1) {
    			if (pf_status.debug >= LOG_DEBUG) {
    				log(LOG_DEBUG,
    				    "pf: pf_map_addr_states_increase: "
    				    "selected address ");
    				pf_print_host(naddr, 0, af);
    				addlog(". Failed to increase count!\n");
    			}
    			return (-1);
    		}
    	} else if (rpool->addr.type == PF_ADDR_DYNIFTL) {
    		if (pfr_states_increase(rpool->addr.p.dyn->pfid_kt,
    		    naddr, af) == -1) {
    			if (pf_status.debug >= LOG_DEBUG) {
    				log(LOG_DEBUG,
    				    "pf: pf_map_addr_states_increase: "
    				    "selected address ");
    				pf_print_host(naddr, 0, af);
    				addlog(". Failed to increase count!\n");
    			}
    			return (-1);
    		}
    	}
    	return (0);
    }
    
    int
    pf_get_transaddr(struct pf_rule *r, struct pf_pdesc *pd,
        struct pf_src_node **sns, struct pf_rule **nr)
    {
    	struct pf_addr	naddr;
    	u_int16_t	nport;
    
    #ifdef INET6
    	if (pd->af != pd->naf)
    		return (pf_get_transaddr_af(r, pd, sns));
    #endif /* INET6 */
    
    	if (r->nat.addr.type != PF_ADDR_NONE) {
    		/* XXX is this right? what if rtable is changed at the same
    		 * XXX time? where do I need to figure out the sport? */
    		nport = 0;
    		if (pf_get_sport(pd, r, &naddr, &nport,
    		    r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
    			DPFPRINTF(LOG_NOTICE,
    			    "pf: NAT proxy port allocation (%u-%u) failed",
    			    r->nat.proxy_port[0],
    			    r->nat.proxy_port[1]);
    			return (-1);
    		}
    		*nr = r;
    		pf_addrcpy(&pd->nsaddr, &naddr, pd->af);
    		pd->nsport = nport;
    	}
    	if (r->rdr.addr.type != PF_ADDR_NONE) {
    		if (pf_map_addr(pd->af, r, &pd->nsaddr, &naddr, NULL, sns,
    		    &r->rdr, PF_SN_RDR))
    			return (-1);
    		if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
    			pf_poolmask(&naddr, &naddr,  &r->rdr.addr.v.a.mask,
    			    &pd->ndaddr, pd->af);
    
    		nport = 0;
    		if (r->rdr.proxy_port[1]) {
    			u_int32_t	tmp_nport;
    			u_int16_t	div;
    
    			div = r->rdr.proxy_port[1] - r->rdr.proxy_port[0] + 1;
    			div = (div == 0) ? 1 : div;
    
    			tmp_nport = ((ntohs(pd->ndport) - ntohs(r->dst.port[0])) % div) +
    			    r->rdr.proxy_port[0];
    
    			/* wrap around if necessary */
    			if (tmp_nport > 65535)
    				tmp_nport -= 65535;
    			nport = htons((u_int16_t)tmp_nport);
    		} else if (r->rdr.proxy_port[0])
    			nport = htons(r->rdr.proxy_port[0]);
    		*nr = r;
    		pf_addrcpy(&pd->ndaddr, &naddr, pd->af);
    		if (nport)
    			pd->ndport = nport;
    	}
    
    	return (0);
    }
    
    #ifdef INET6
    int
    pf_get_transaddr_af(struct pf_rule *r, struct pf_pdesc *pd,
        struct pf_src_node **sns)
    {
    	struct pf_addr	ndaddr, nsaddr, naddr;
    	u_int16_t	nport;
    	int		prefixlen = 96;
    
    	if (pf_status.debug >= LOG_INFO) {
    		log(LOG_INFO, "pf: af-to %s %s, ",
    		    pd->naf == AF_INET ? "inet" : "inet6",
    		    r->rdr.addr.type == PF_ADDR_NONE ? "nat" : "rdr");
    		pf_print_host(&pd->nsaddr, pd->nsport, pd->af);
    		addlog(" -> ");
    		pf_print_host(&pd->ndaddr, pd->ndport, pd->af);
    		addlog("\n");
    	}
    
    	if (r->nat.addr.type == PF_ADDR_NONE)
    		panic("pf_get_transaddr_af: no nat pool for source address");
    
    	/* get source address and port */
    	nport = 0;
    	if (pf_get_sport(pd, r, &nsaddr, &nport,
    	    r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
    		DPFPRINTF(LOG_NOTICE,
    		    "pf: af-to NAT proxy port allocation (%u-%u) failed",
    		    r->nat.proxy_port[0],
    		    r->nat.proxy_port[1]);
    		return (-1);
    	}
    	pd->nsport = nport;
    
    	if (pd->proto == IPPROTO_ICMPV6 && pd->naf == AF_INET) {
    		if (pd->dir == PF_IN) {
    			pd->ndport = ntohs(pd->ndport);
    			if (pd->ndport == ICMP6_ECHO_REQUEST)
    				pd->ndport = ICMP_ECHO;
    			else if (pd->ndport == ICMP6_ECHO_REPLY)
    				pd->ndport = ICMP_ECHOREPLY;
    			pd->ndport = htons(pd->ndport);
    		} else {
    			pd->nsport = ntohs(pd->nsport);
    			if (pd->nsport == ICMP6_ECHO_REQUEST)
    				pd->nsport = ICMP_ECHO;
    			else if (pd->nsport == ICMP6_ECHO_REPLY)
    				pd->nsport = ICMP_ECHOREPLY;
    			pd->nsport = htons(pd->nsport);
    		}
    	} else if (pd->proto == IPPROTO_ICMP && pd->naf == AF_INET6) {
    		if (pd->dir == PF_IN) {
    			pd->ndport = ntohs(pd->ndport);
    			if (pd->ndport == ICMP_ECHO)
    				pd->ndport = ICMP6_ECHO_REQUEST;
    			else if (pd->ndport == ICMP_ECHOREPLY)
    				pd->ndport = ICMP6_ECHO_REPLY;
    			pd->ndport = htons(pd->ndport);
    		} else {
    			pd->nsport = ntohs(pd->nsport);
    			if (pd->nsport == ICMP_ECHO)
    				pd->nsport = ICMP6_ECHO_REQUEST;
    			else if (pd->nsport == ICMP_ECHOREPLY)
    				pd->nsport = ICMP6_ECHO_REPLY;
    			pd->nsport = htons(pd->nsport);
    		}
    	}
    
    	/* get the destination address and port */
    	if (r->rdr.addr.type != PF_ADDR_NONE) {
    		if (pf_map_addr(pd->naf, r, &nsaddr, &naddr, NULL, sns,
    		    &r->rdr, PF_SN_RDR))
    			return (-1);
    		if (r->rdr.proxy_port[0])
    			pd->ndport = htons(r->rdr.proxy_port[0]);
    
    		if (pd->naf == AF_INET) {
    			/* The prefix is the IPv4 rdr address */
    			prefixlen = in_mask2len((struct in_addr *)
    			    &r->rdr.addr.v.a.mask);
    			inet_nat46(pd->naf, &pd->ndaddr,
    			    &ndaddr, &naddr, prefixlen);
    		} else {
    			/* The prefix is the IPv6 rdr address */
    			prefixlen =
    			    in6_mask2len((struct in6_addr *)
    			    &r->rdr.addr.v.a.mask, NULL);
    			inet_nat64(pd->naf, &pd->ndaddr,
    			    &ndaddr, &naddr, prefixlen);
    		}
    	} else {
    		if (pd->naf == AF_INET) {
    			/* The prefix is the IPv6 dst address */
    			prefixlen =
    			    in6_mask2len((struct in6_addr *)
    			    &r->dst.addr.v.a.mask, NULL);
    			if (prefixlen < 32)
    				prefixlen = 96;
    			inet_nat64(pd->naf, &pd->ndaddr,
    			    &ndaddr, &pd->ndaddr, prefixlen);
    		} else {
    			/*
    			 * The prefix is the IPv6 nat address
    			 * (that was stored in pd->nsaddr)
    			 */
    			prefixlen = in6_mask2len((struct in6_addr *)
    			    &r->nat.addr.v.a.mask, NULL);
    			if (prefixlen > 96)
    				prefixlen = 96;
    			inet_nat64(pd->naf, &pd->ndaddr,
    			    &ndaddr, &nsaddr, prefixlen);
    		}
    	}
    
    	pf_addrcpy(&pd->nsaddr, &nsaddr, pd->naf);
    	pf_addrcpy(&pd->ndaddr, &ndaddr, pd->naf);
    
    	if (pf_status.debug >= LOG_INFO) {
    		log(LOG_INFO, "pf: af-to %s %s done, prefixlen %d, ",
    		    pd->naf == AF_INET ? "inet" : "inet6",
    		    r->rdr.addr.type == PF_ADDR_NONE ? "nat" : "rdr",
    		    prefixlen);
    		pf_print_host(&pd->nsaddr, pd->nsport, pd->naf);
    		addlog(" -> ");
    		pf_print_host(&pd->ndaddr, pd->ndport, pd->naf);
    		addlog("\n");
    	}
    
    	return (0);
    }
    #endif /* INET6 */
    
    int
    pf_postprocess_addr(struct pf_state *cur)
    {
    	struct pf_rule		*nr;
    	struct pf_state_key	*sks;
    	struct pf_pool		 rpool;
    	struct pf_addr		 lookup_addr;
    	int			 slbcount = -1;
    
    	nr = cur->natrule.ptr;
    
    	if (nr == NULL)
    		return (0);
    
    	/* decrease counter */
    
    	sks = cur->key[PF_SK_STACK];
    
    	/* check for outgoing or ingoing balancing */
    	if (nr->rt == PF_ROUTETO)
    		lookup_addr = cur->rt_addr;
    	else if (sks != NULL)
    		lookup_addr = sks->addr[1];
    	else {
    		if (pf_status.debug >= LOG_DEBUG) {
    			log(LOG_DEBUG, "pf: %s: unable to obtain address",
    			    __func__);
    		}
    		return (1);
    	}
    
    	/* check for appropriate pool */
    	if (nr->rdr.addr.type != PF_ADDR_NONE)
    		rpool = nr->rdr;
    	else if (nr->nat.addr.type != PF_ADDR_NONE)
    		rpool = nr->nat;
    	else if (nr->route.addr.type != PF_ADDR_NONE)
    		rpool = nr->route;
    	else
    		return (0);
    
    	if (((rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_LEASTSTATES))
    		return (0);
    
    	if (rpool.addr.type == PF_ADDR_TABLE) {
    		if ((slbcount = pfr_states_decrease(
    		    rpool.addr.p.tbl,
    		    &lookup_addr, sks->af)) == -1) {
    			if (pf_status.debug >= LOG_DEBUG) {
    				log(LOG_DEBUG, "pf: %s: selected address ",
    				    __func__);
    				pf_print_host(&lookup_addr,
    				    sks->port[0], sks->af);
    				addlog(". Failed to "
    				    "decrease count!\n");
    			}
    			return (1);
    		}
    	} else if (rpool.addr.type == PF_ADDR_DYNIFTL) {
    		if ((slbcount = pfr_states_decrease(
    		    rpool.addr.p.dyn->pfid_kt,
    		    &lookup_addr, sks->af)) == -1) {
    			if (pf_status.debug >= LOG_DEBUG) {
    				log(LOG_DEBUG, "pf: %s: selected address ",
    				    __func__);
    				pf_print_host(&lookup_addr,
    				    sks->port[0], sks->af);
    				addlog(". Failed to "
    				    "decrease count!\n");
    			}
    			return (1);
    		}
    	}
    	if (slbcount > -1) {
    		if (pf_status.debug >= LOG_INFO) {
    			log(LOG_INFO, "pf: %s: selected address ", __func__);
    			pf_print_host(&lookup_addr, sks->port[0],
    			    sks->af);
    			addlog(" decreased state count to %u\n",
    			    slbcount);
    		}
    	}
    	return (0);
    }