#if defined(KERNEL) || defined(_KERNEL)
# undef KERNEL
# undef _KERNEL
# define KERNEL 1
# define _KERNEL 1
#endif
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/file.h>
#if defined(__NetBSD__) && (NetBSD >= 199905) && !defined(IPFILTER_LKM) && \
defined(_KERNEL)
# include "opt_ipfilter_log.h"
#endif
#if !defined(_KERNEL)
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# define _KERNEL
# ifdef __OpenBSD__
struct file;
# endif
# include <sys/uio.h>
# undef _KERNEL
#endif
#if defined(_KERNEL) && (__FreeBSD_version >= 220000)
# include <sys/filio.h>
# include <sys/fcntl.h>
#else
# include <sys/ioctl.h>
#endif
#if !defined(AIX)
# include <sys/fcntl.h>
#endif
#if !defined(linux)
# include <sys/protosw.h>
#endif
#include <sys/socket.h>
#if defined(_KERNEL)
# include <sys/systm.h>
# if !defined(__SVR4) && !defined(__svr4__)
# include <sys/mbuf.h>
# endif
#endif
#if defined(__SVR4) || defined(__svr4__)
# include <sys/filio.h>
# include <sys/byteorder.h>
# ifdef _KERNEL
# include <sys/dditypes.h>
# endif
# include <sys/stream.h>
# include <sys/kmem.h>
#endif
#if __FreeBSD_version >= 300000
# include <sys/queue.h>
#endif
#include <net/if.h>
#if __FreeBSD_version >= 300000
# include <net/if_var.h>
# if defined(_KERNEL) && !defined(IPFILTER_LKM)
# include "opt_ipfilter.h"
# endif
#endif
#ifdef sun
# include <net/af.h>
#endif
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#ifdef RFC1825
# include <vpn/md5.h>
# include <vpn/ipsec.h>
extern struct ifnet vpnif;
#endif
#if !defined(linux)
# include <netinet/ip_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include "netinet/ip_compat.h"
#include <netinet/tcpip.h>
#include "netinet/ip_fil.h"
#include "netinet/ip_nat.h"
#include "netinet/ip_frag.h"
#include "netinet/ip_state.h"
#include "netinet/ip_proxy.h"
#include "netinet/ipf_stack.h"
#ifdef IPFILTER_SYNC
#include "netinet/ip_sync.h"
#endif
#if (__FreeBSD_version >= 300000)
# include <sys/malloc.h>
#endif
#undef SOCKADDR_IN
#define SOCKADDR_IN struct sockaddr_in
#if !defined(lint)
static const char rcsid[] = "@(#)$Id: ip_nat6.c,v 1.2 2008/02/14 21:05:50 darrenr Exp $";
#endif
static hostmap_t *nat6_hostmap __P((ipnat_t *, i6addr_t *, i6addr_t *,
i6addr_t *, u_32_t, ipf_stack_t *));
static INLINE int nat6_newmap __P((fr_info_t *, nat_t *, natinfo_t *));
static INLINE int nat6_newrdr __P((fr_info_t *, nat_t *, natinfo_t *));
static INLINE int nat6_finalise __P((fr_info_t *, nat_t *, natinfo_t *,
tcphdr_t *, nat_t **, int));
static void nat6_tabmove __P((nat_t *, ipf_stack_t *));
static int nat6_match __P((fr_info_t *, ipnat_t *));
static INLINE int nat_icmpquerytype6 __P((int));
void nat6_addrdr(n, ifs)
ipnat_t *n;
ipf_stack_t *ifs;
{
ipnat_t **np;
i6addr_t j;
u_int hv;
int k;
k = count6bits(n->in_out[1].i6);
if ((k >= 0) && (k != 128))
ifs->ifs_rdr6_masks[k >> 5] |= 1 << (k & 31);
IP6_AND(&n->in_out[0], &n->in_out[1], &j);
hv = NAT_HASH_FN6(&j, 0, ifs->ifs_ipf_rdrrules_sz);
np = ifs->ifs_rdr_rules + hv;
while (*np != NULL)
np = &(*np)->in_rnext;
n->in_rnext = NULL;
n->in_prnext = np;
n->in_hv = hv;
*np = n;
}
void nat6_addnat(n, ifs)
ipnat_t *n;
ipf_stack_t *ifs;
{
ipnat_t **np;
i6addr_t j;
u_int hv;
int k;
k = count6bits(n->in_in[1].i6);
if ((k >= 0) && (k != 128))
ifs->ifs_nat6_masks[k >> 5] |= 1 << (k & 31);
IP6_AND(&n->in_in[0], &n->in_in[1], &j);
hv = NAT_HASH_FN6(&j, 0, ifs->ifs_ipf_natrules_sz);
np = ifs->ifs_nat_rules + hv;
while (*np != NULL)
np = &(*np)->in_mnext;
n->in_mnext = NULL;
n->in_pmnext = np;
n->in_hv = hv;
*np = n;
}
static struct hostmap *nat6_hostmap(np, src, dst, map, port, ifs)
ipnat_t *np;
i6addr_t *src, *dst, *map;
u_32_t port;
ipf_stack_t *ifs;
{
hostmap_t *hm;
u_int hv;
hv = (src->i6[3] ^ dst->i6[3]);
hv += (src->i6[2] ^ dst->i6[2]);
hv += (src->i6[1] ^ dst->i6[1]);
hv += (src->i6[0] ^ dst->i6[0]);
hv += src->i6[3];
hv += src->i6[2];
hv += src->i6[1];
hv += src->i6[0];
hv += dst->i6[3];
hv += dst->i6[2];
hv += dst->i6[1];
hv += dst->i6[0];
hv %= HOSTMAP_SIZE;
for (hm = ifs->ifs_maptable[hv]; hm; hm = hm->hm_next)
if (IP6_EQ(&hm->hm_srcip6, src) &&
IP6_EQ(&hm->hm_dstip6, dst) &&
((np == NULL) || (np == hm->hm_ipnat)) &&
((port == 0) || (port == hm->hm_port))) {
hm->hm_ref++;
return hm;
}
if (np == NULL)
return NULL;
KMALLOC(hm, hostmap_t *);
if (hm) {
hm->hm_hnext = ifs->ifs_ipf_hm_maplist;
hm->hm_phnext = &ifs->ifs_ipf_hm_maplist;
if (ifs->ifs_ipf_hm_maplist != NULL)
ifs->ifs_ipf_hm_maplist->hm_phnext = &hm->hm_hnext;
ifs->ifs_ipf_hm_maplist = hm;
hm->hm_next = ifs->ifs_maptable[hv];
hm->hm_pnext = ifs->ifs_maptable + hv;
if (ifs->ifs_maptable[hv] != NULL)
ifs->ifs_maptable[hv]->hm_pnext = &hm->hm_next;
ifs->ifs_maptable[hv] = hm;
hm->hm_ipnat = np;
hm->hm_src = *src;
hm->hm_dst = *dst;
hm->hm_map = *map;
hm->hm_ref = 1;
hm->hm_port = port;
hm->hm_v = 6;
}
return hm;
}
static INLINE int nat6_newmap(fin, nat, ni)
fr_info_t *fin;
nat_t *nat;
natinfo_t *ni;
{
u_short st_port, dport, sport, port, sp, dp;
i6addr_t in, st_ip;
hostmap_t *hm;
u_32_t flags;
ipnat_t *np;
nat_t *natl;
int l;
ipf_stack_t *ifs = fin->fin_ifs;
l = 0;
hm = NULL;
np = ni->nai_np;
st_ip = np->in_next6;
st_port = np->in_pnext;
flags = ni->nai_flags;
sport = ni->nai_sport;
dport = ni->nai_dport;
do {
port = 0;
in = np->in_next6;
if (l == 0) {
hm = nat6_hostmap(np, &fin->fin_src6, &fin->fin_dst6,
&in, 0, ifs);
if (hm != NULL)
in = hm->hm_map;
} else if ((l == 1) && (hm != NULL)) {
fr_hostmapdel(&hm);
}
nat->nat_hm = hm;
if (IP6_ISONES(&np->in_out[1]) && (np->in_pnext == 0)) {
if (l > 0)
return -1;
}
if (np->in_redir == NAT_BIMAP &&
IP6_EQ(&np->in_in[1], &np->in_out[1])) {
i6addr_t temp;
temp.i6[0] = fin->fin_src6.i6[0] &
~np->in_in[1].i6[0];
temp.i6[1] = fin->fin_src6.i6[1] &
~np->in_in[1].i6[1];
temp.i6[2] = fin->fin_src6.i6[2] &
~np->in_in[1].i6[2];
temp.i6[3] = fin->fin_src6.i6[3] &
~np->in_in[1].i6[3];
in = np->in_out[0];
IP6_MERGE(&in, &temp, &np->in_in[0]);
#ifdef NEED_128BIT_MATH
} else if (np->in_redir & NAT_MAPBLK) {
if ((l >= np->in_ppip) || ((l > 0) &&
!(flags & IPN_TCPUDP)))
return -1;
IP6_MASK(&in, &fin->fin_src6, &np->in_in[1]);
in = ntol(in);
inb = in;
in /= np->in_ippip;
in &= ntohl(~np->in_out[1]);
in += ntohl(np->in_out[0]);
if ((flags & IPN_TCPUDP) &&
(np->in_ppip != 0)) {
port = ntohs(sport) + l;
port %= np->in_ppip;
port += np->in_ppip *
(inb.s_addr % np->in_ippip);
port += MAPBLK_MINPORT;
port = htons(port);
}
#endif
} else if (IP6_ISZERO(&np->in_out[0]) &&
IP6_ISONES(&np->in_out[1])) {
if ((l > 0) ||
fr_ifpaddr(6, FRI_NORMAL, fin->fin_ifp,
(void *)&in, NULL, fin->fin_ifs) == -1)
return -1;
} else if (IP6_ISZERO(&np->in_out[0]) &&
IP6_ISZERO(&np->in_out[1])) {
if (l > 0)
return -1;
in = fin->fin_src6;
} else if (!IP6_ISONES(&np->in_out[1]) &&
(np->in_pnext == 0) && ((l > 0) || (hm == NULL))) {
IP6_INC(&np->in_next6);
}
natl = NULL;
if ((flags & IPN_TCPUDP) &&
((np->in_redir & NAT_MAPBLK) == 0) &&
(np->in_flags & IPN_AUTOPORTMAP)) {
;
#ifdef NEED_128BIT_MATH
if ((l > 0) && (l % np->in_ppip == 0)) {
if (l > np->in_space) {
return -1;
} else if ((l > np->in_ppip) &&
!IP6_ISONES(&np->in_out[1])) {
IP6_INC(&np->in_next6);
}
}
if (np->in_ppip != 0) {
port = ntohs(sport);
port += (l % np->in_ppip);
port %= np->in_ppip;
port += np->in_ppip *
(ntohl(fin->fin_src6) %
np->in_ippip);
port += MAPBLK_MINPORT;
port = htons(port);
}
#endif
} else if (((np->in_redir & NAT_MAPBLK) == 0) &&
(flags & IPN_TCPUDPICMP) && (np->in_pnext != 0)) {
if (np->in_flags & IPN_SEQUENTIAL) {
port = np->in_pnext;
} else {
port = ipf_random() % (ntohs(np->in_pmax) -
ntohs(np->in_pmin));
port += ntohs(np->in_pmin);
}
port = htons(port);
np->in_pnext++;
if (np->in_pnext > ntohs(np->in_pmax)) {
np->in_pnext = ntohs(np->in_pmin);
if (!IP6_ISONES(&np->in_out[1])) {
IP6_INC(&np->in_next6);
}
}
}
if (np->in_flags & IPN_IPRANGE) {
if (IP6_GT(&np->in_next6, &np->in_out[1]))
np->in_next6 = np->in_out[0];
} else {
i6addr_t a1, a2;
a1 = np->in_next6;
IP6_INC(&a1);
IP6_AND(&a1, &np->in_out[1], &a2);
if (!IP6_ISONES(&np->in_out[1]) &&
IP6_GT(&a2, &np->in_out[0])) {
IP6_ADD(&np->in_out[0], 1, &np->in_next6);
}
}
if ((port == 0) && (flags & (IPN_TCPUDPICMP|IPN_ICMPQUERY)))
port = sport;
sp = fin->fin_data[0];
dp = fin->fin_data[1];
fin->fin_data[0] = fin->fin_data[1];
fin->fin_data[1] = htons(port);
natl = nat6_inlookup(fin, flags & ~(SI_WILDP|NAT_SEARCH),
(u_int)fin->fin_p, &fin->fin_dst6.in6, &in.in6);
fin->fin_data[0] = sp;
fin->fin_data[1] = dp;
if ((natl != NULL) &&
(np->in_pnext != 0) && (st_port == np->in_pnext) &&
!IP6_ISZERO(&np->in_next6) &&
IP6_EQ(&st_ip, &np->in_next6))
return -1;
l++;
} while (natl != NULL);
if (np->in_space > 0)
np->in_space--;
nat->nat_inip6 = fin->fin_src6;
nat->nat_outip6 = in;
nat->nat_oip6 = fin->fin_dst6;
if (nat->nat_hm == NULL)
nat->nat_hm = nat6_hostmap(np, &fin->fin_src6, &fin->fin_dst6,
&nat->nat_outip6, 0, ifs);
if (flags & IPN_TCPUDP) {
nat->nat_inport = sport;
nat->nat_outport = port;
nat->nat_oport = dport;
((tcphdr_t *)fin->fin_dp)->th_sport = port;
} else if (flags & IPN_ICMPQUERY) {
((struct icmp6_hdr *)fin->fin_dp)->icmp6_id = port;
nat->nat_inport = port;
nat->nat_outport = port;
}
ni->nai_port = port;
ni->nai_nport = dport;
return 0;
}
static INLINE int nat6_newrdr(fin, nat, ni)
fr_info_t *fin;
nat_t *nat;
natinfo_t *ni;
{
u_short nport, dport, sport;
i6addr_t in;
u_short sp, dp;
hostmap_t *hm;
u_32_t flags;
ipnat_t *np;
nat_t *natl;
int move;
ipf_stack_t *ifs = fin->fin_ifs;
move = 1;
hm = NULL;
in.i6[0] = 0;
in.i6[1] = 0;
in.i6[2] = 0;
in.i6[3] = 0;
np = ni->nai_np;
flags = ni->nai_flags;
sport = ni->nai_sport;
dport = ni->nai_dport;
if ((np->in_flags & (IPN_ROUNDR|IPN_STICKY)) ==
(IPN_ROUNDR|IPN_STICKY)) {
hm = nat6_hostmap(NULL, &fin->fin_src6, &fin->fin_dst6, &in,
(u_32_t)dport, ifs);
if (hm != NULL) {
in = hm->hm_map;
np = hm->hm_ipnat;
ni->nai_np = np;
move = 0;
}
}
if (np->in_flags & IPN_SPLIT) {
in = np->in_next6;
if ((np->in_flags & (IPN_ROUNDR|IPN_STICKY)) == IPN_STICKY) {
hm = nat6_hostmap(np, &fin->fin_src6, &fin->fin_dst6,
&in, (u_32_t)dport, ifs);
if (hm != NULL) {
in = hm->hm_map;
move = 0;
}
}
if (hm == NULL || hm->hm_ref == 1) {
if (IP6_EQ(&np->in_in[0], &in)) {
np->in_next6 = np->in_in[1];
move = 0;
} else {
np->in_next6 = np->in_in[0];
}
}
} else if (IP6_ISZERO(&np->in_in[0]) &&
IP6_ISONES(&np->in_in[1])) {
if (fr_ifpaddr(6, FRI_NORMAL, fin->fin_ifp, (void *)&in, NULL,
fin->fin_ifs) == -1)
return -1;
} else if (IP6_ISZERO(&np->in_in[0]) &&
IP6_ISZERO(&np->in_in[1])) {
in = fin->fin_dst6;
} else if (np->in_redir == NAT_BIMAP &&
IP6_EQ(&np->in_in[1], &np->in_out[1])) {
i6addr_t temp;
temp.i6[0] = fin->fin_dst6.i6[0] & ~np->in_in[1].i6[0];
temp.i6[1] = fin->fin_dst6.i6[1] & ~np->in_in[1].i6[1];
temp.i6[2] = fin->fin_dst6.i6[2] & ~np->in_in[1].i6[2];
temp.i6[3] = fin->fin_dst6.i6[3] & ~np->in_in[1].i6[3];
in = np->in_in[0];
IP6_MERGE(&in, &temp, &np->in_in[1]);
} else {
in = np->in_in[0];
}
if ((np->in_pnext == 0) || ((flags & NAT_NOTRULEPORT) != 0))
nport = dport;
else {
if (((np->in_flags & IPN_FIXEDDPORT) == 0) &&
(np->in_pmin != np->in_pmax)) {
nport = ntohs(dport) - ntohs(np->in_pmin) +
ntohs(np->in_pnext);
nport = htons(nport);
} else
nport = np->in_pnext;
}
if (IP6_ISZERO(&in)) {
if (nport == dport)
return -1;
in = fin->fin_dst6;
}
sp = fin->fin_data[0];
dp = fin->fin_data[1];
fin->fin_data[1] = fin->fin_data[0];
fin->fin_data[0] = ntohs(nport);
natl = nat6_outlookup(fin, flags & ~(SI_WILDP|NAT_SEARCH),
(u_int)fin->fin_p, &in.in6, &fin->fin_src6.in6);
fin->fin_data[0] = sp;
fin->fin_data[1] = dp;
if (natl != NULL)
return -1;
nat->nat_inip6 = in;
nat->nat_outip6 = fin->fin_dst6;
nat->nat_oip6 = fin->fin_src6;
if ((nat->nat_hm == NULL) && ((np->in_flags & IPN_STICKY) != 0))
nat->nat_hm = nat6_hostmap(np, &fin->fin_src6,
&fin->fin_dst6, &in, (u_32_t)dport, ifs);
ni->nai_nport = nport;
ni->nai_port = sport;
if (flags & IPN_TCPUDP) {
nat->nat_inport = nport;
nat->nat_outport = dport;
nat->nat_oport = sport;
((tcphdr_t *)fin->fin_dp)->th_dport = nport;
} else if (flags & IPN_ICMPQUERY) {
((struct icmp6_hdr *)fin->fin_dp)->icmp6_id = nport;
nat->nat_inport = nport;
nat->nat_outport = nport;
}
return move;
}
nat_t *nat6_new(fin, np, natsave, flags, direction)
fr_info_t *fin;
ipnat_t *np;
nat_t **natsave;
u_int flags;
int direction;
{
tcphdr_t *tcp = NULL;
hostmap_t *hm = NULL;
nat_t *nat, *natl;
u_int nflags;
natinfo_t ni;
int move;
ipf_stack_t *ifs = fin->fin_ifs;
if (NAT_TAB_WATER_LEVEL(ifs) > ifs->ifs_nat_flush_level_hi)
ifs->ifs_nat_doflush = 1;
if (ifs->ifs_nat_stats.ns_inuse >= ifs->ifs_ipf_nattable_max) {
ifs->ifs_nat_stats.ns_memfail++;
return NULL;
}
move = 1;
nflags = np->in_flags & flags;
nflags &= NAT_FROMRULE;
ni.nai_np = np;
ni.nai_nflags = nflags;
ni.nai_flags = flags;
KMALLOC(nat, nat_t *);
if (nat == NULL) {
ifs->ifs_nat_stats.ns_memfail++;
if (ifs->ifs_ipf_nattable_max > ifs->ifs_ipf_nattable_sz) {
ifs->ifs_ipf_nattable_max =
ifs->ifs_nat_stats.ns_inuse - 100;
printf("ipf_nattable_max reduced to %d\n",
ifs->ifs_ipf_nattable_max);
}
return NULL;
}
if (flags & IPN_TCPUDP) {
tcp = fin->fin_dp;
ni.nai_sport = htons(fin->fin_sport);
ni.nai_dport = htons(fin->fin_dport);
} else if (flags & IPN_ICMPQUERY) {
ni.nai_sport = ((struct icmp6_hdr *)fin->fin_dp)->icmp6_id;
ni.nai_dport = ni.nai_sport;
}
bzero((char *)nat, sizeof (*nat));
nat->nat_flags = flags;
nat->nat_redir = np->in_redir;
if ((flags & NAT_SLAVE) == 0) {
MUTEX_ENTER(&ifs->ifs_ipf_nat_new);
}
if (direction == NAT_OUTBOUND) {
natl = nat6_outlookup(fin, nflags, (u_int)fin->fin_p,
&fin->fin_src6.in6, &fin->fin_dst6.in6);
if (natl != NULL) {
KFREE(nat);
nat = natl;
goto done;
}
move = nat6_newmap(fin, nat, &ni);
if (move == -1)
goto badnat;
np = ni.nai_np;
} else {
natl = nat6_inlookup(fin, nflags, (u_int)fin->fin_p,
&fin->fin_src6.in6, &fin->fin_dst6.in6);
if (natl != NULL) {
KFREE(nat);
nat = natl;
goto done;
}
move = nat6_newrdr(fin, nat, &ni);
if (move == -1)
goto badnat;
np = ni.nai_np;
}
if ((move == 1) && (np->in_flags & IPN_ROUNDR)) {
if (np->in_redir == NAT_REDIRECT) {
nat_delrdr(np);
nat6_addrdr(np, ifs);
} else if (np->in_redir == NAT_MAP) {
nat_delnat(np);
nat6_addnat(np, ifs);
}
}
if (nat6_finalise(fin, nat, &ni, tcp, natsave, direction) == -1) {
goto badnat;
}
nat_calc_chksum_diffs(nat);
if (flags & SI_WILDP)
ifs->ifs_nat_stats.ns_wilds++;
goto done;
badnat:
ifs->ifs_nat_stats.ns_badnat++;
if ((hm = nat->nat_hm) != NULL)
fr_hostmapdel(&hm);
KFREE(nat);
nat = NULL;
done:
if ((flags & NAT_SLAVE) == 0) {
MUTEX_EXIT(&ifs->ifs_ipf_nat_new);
}
return nat;
}
static INLINE int nat6_finalise(fin, nat, ni, tcp, natsave, direction)
fr_info_t *fin;
nat_t *nat;
natinfo_t *ni;
tcphdr_t *tcp;
nat_t **natsave;
int direction;
{
frentry_t *fr;
ipnat_t *np;
ipf_stack_t *ifs = fin->fin_ifs;
np = ni->nai_np;
COPYIFNAME(fin->fin_ifp, nat->nat_ifnames[0], fin->fin_v);
#ifdef IPFILTER_SYNC
if ((nat->nat_flags & SI_CLONE) == 0)
nat->nat_sync = ipfsync_new(SMC_NAT, fin, nat);
#endif
nat->nat_me = natsave;
nat->nat_dir = direction;
nat->nat_ifps[0] = np->in_ifps[0];
nat->nat_ifps[1] = np->in_ifps[1];
nat->nat_ptr = np;
nat->nat_p = fin->fin_p;
nat->nat_v = fin->fin_v;
nat->nat_mssclamp = np->in_mssclamp;
fr = fin->fin_fr;
nat->nat_fr = fr;
nat->nat_v = 6;
#ifdef IPF_V6_PROXIES
if ((np->in_apr != NULL) && ((ni->nai_flags & NAT_SLAVE) == 0))
if (appr_new(fin, nat) == -1)
return -1;
#endif
if (nat6_insert(nat, fin->fin_rev, ifs) == 0) {
if (ifs->ifs_nat_logging)
nat_log(nat, (u_int)np->in_redir, ifs);
np->in_use++;
if (fr != NULL) {
MUTEX_ENTER(&fr->fr_lock);
fr->fr_ref++;
MUTEX_EXIT(&fr->fr_lock);
}
return 0;
}
return -1;
}
int nat6_insert(nat, rev, ifs)
nat_t *nat;
int rev;
ipf_stack_t *ifs;
{
u_int hv1, hv2;
nat_t **natp;
if ((nat->nat_flags & (SI_W_SPORT|SI_W_DPORT)) == 0) {
hv1 = NAT_HASH_FN6(&nat->nat_inip6, nat->nat_inport,
0xffffffff);
hv1 = NAT_HASH_FN6(&nat->nat_oip6, hv1 + nat->nat_oport,
ifs->ifs_ipf_nattable_sz);
hv2 = NAT_HASH_FN6(&nat->nat_outip6, nat->nat_outport,
0xffffffff);
hv2 = NAT_HASH_FN6(&nat->nat_oip6, hv2 + nat->nat_oport,
ifs->ifs_ipf_nattable_sz);
} else {
hv1 = NAT_HASH_FN6(&nat->nat_inip6, 0, 0xffffffff);
hv1 = NAT_HASH_FN6(&nat->nat_oip6, hv1,
ifs->ifs_ipf_nattable_sz);
hv2 = NAT_HASH_FN6(&nat->nat_outip6, 0, 0xffffffff);
hv2 = NAT_HASH_FN6(&nat->nat_oip6, hv2,
ifs->ifs_ipf_nattable_sz);
}
if ((ifs->ifs_nat_stats.ns_bucketlen[0][hv1] >=
ifs->ifs_fr_nat_maxbucket) ||
(ifs->ifs_nat_stats.ns_bucketlen[1][hv2] >=
ifs->ifs_fr_nat_maxbucket)) {
return -1;
}
nat->nat_hv[0] = hv1;
nat->nat_hv[1] = hv2;
MUTEX_INIT(&nat->nat_lock, "nat entry lock");
nat->nat_rev = rev;
nat->nat_ref = 1;
nat->nat_bytes[0] = 0;
nat->nat_pkts[0] = 0;
nat->nat_bytes[1] = 0;
nat->nat_pkts[1] = 0;
nat->nat_ifnames[0][LIFNAMSIZ - 1] = '\0';
nat->nat_ifps[0] = fr_resolvenic(nat->nat_ifnames[0], 6, ifs);
if (nat->nat_ifnames[1][0] !='\0') {
nat->nat_ifnames[1][LIFNAMSIZ - 1] = '\0';
nat->nat_ifps[1] = fr_resolvenic(nat->nat_ifnames[1], 6, ifs);
} else {
(void) strncpy(nat->nat_ifnames[1], nat->nat_ifnames[0],
LIFNAMSIZ);
nat->nat_ifnames[1][LIFNAMSIZ - 1] = '\0';
nat->nat_ifps[1] = nat->nat_ifps[0];
}
nat->nat_next = ifs->ifs_nat_instances;
nat->nat_pnext = &ifs->ifs_nat_instances;
if (ifs->ifs_nat_instances)
ifs->ifs_nat_instances->nat_pnext = &nat->nat_next;
ifs->ifs_nat_instances = nat;
natp = &ifs->ifs_nat_table[0][hv1];
if (*natp)
(*natp)->nat_phnext[0] = &nat->nat_hnext[0];
nat->nat_phnext[0] = natp;
nat->nat_hnext[0] = *natp;
*natp = nat;
ifs->ifs_nat_stats.ns_bucketlen[0][hv1]++;
natp = &ifs->ifs_nat_table[1][hv2];
if (*natp)
(*natp)->nat_phnext[1] = &nat->nat_hnext[1];
nat->nat_phnext[1] = natp;
nat->nat_hnext[1] = *natp;
*natp = nat;
ifs->ifs_nat_stats.ns_bucketlen[1][hv2]++;
fr_setnatqueue(nat, rev, ifs);
ifs->ifs_nat_stats.ns_added++;
ifs->ifs_nat_stats.ns_inuse++;
return 0;
}
nat_t *nat6_icmperrorlookup(fin, dir)
fr_info_t *fin;
int dir;
{
int flags = 0, minlen;
struct icmp6_hdr *orgicmp;
tcphdr_t *tcp = NULL;
u_short data[2];
nat_t *nat;
ip6_t *oip6;
u_int p;
minlen = 40;
if (!(fin->fin_flx & FI_ICMPERR))
return NULL;
if (fin->fin_plen < ICMP6ERR_IPICMPHLEN)
return NULL;
oip6 = (ip6_t *)((char *)fin->fin_dp + 8);
#ifdef _KERNEL
{
mb_t *m;
m = fin->fin_m;
# if defined(MENTAT)
if ((char *)oip6 + fin->fin_dlen - ICMPERR_ICMPHLEN > (char *)m->b_wptr)
return NULL;
# else
if ((char *)oip6 + fin->fin_dlen - ICMPERR_ICMPHLEN >
(char *)fin->fin_ip + M_LEN(m))
return NULL;
# endif
}
#endif
if (IP6_NEQ(&fin->fin_dst6, &oip6->ip6_src))
return NULL;
p = oip6->ip6_nxt;
if (p == IPPROTO_TCP)
flags = IPN_TCP;
else if (p == IPPROTO_UDP)
flags = IPN_UDP;
else if (p == IPPROTO_ICMPV6) {
orgicmp = (struct icmp6_hdr *)(oip6 + 1);
if (nat_icmpquerytype6(orgicmp->icmp6_type)) {
data[0] = fin->fin_data[0];
data[1] = fin->fin_data[1];
fin->fin_data[0] = 0;
fin->fin_data[1] = orgicmp->icmp6_id;
flags = IPN_ICMPERR|IPN_ICMPQUERY;
if (dir == NAT_INBOUND)
nat = nat6_inlookup(fin, flags, p,
&oip6->ip6_dst, &oip6->ip6_src);
else
nat = nat6_outlookup(fin, flags, p,
&oip6->ip6_dst, &oip6->ip6_src);
fin->fin_data[0] = data[0];
fin->fin_data[1] = data[1];
return nat;
}
}
if (flags & IPN_TCPUDP) {
minlen += 8;
if (fin->fin_plen < ICMPERR_ICMPHLEN + minlen)
return NULL;
data[0] = fin->fin_data[0];
data[1] = fin->fin_data[1];
tcp = (tcphdr_t *)(oip6 + 1);
fin->fin_data[0] = ntohs(tcp->th_dport);
fin->fin_data[1] = ntohs(tcp->th_sport);
if (dir == NAT_INBOUND) {
nat = nat6_inlookup(fin, flags, p,
&oip6->ip6_dst, &oip6->ip6_src);
} else {
nat = nat6_outlookup(fin, flags, p,
&oip6->ip6_dst, &oip6->ip6_src);
}
fin->fin_data[0] = data[0];
fin->fin_data[1] = data[1];
return nat;
}
if (dir == NAT_INBOUND)
return nat6_inlookup(fin, 0, p, &oip6->ip6_dst, &oip6->ip6_src);
else
return nat6_outlookup(fin, 0, p, &oip6->ip6_dst,
&oip6->ip6_src);
}
nat_t *nat6_icmperror(fin, nflags, dir)
fr_info_t *fin;
u_int *nflags;
int dir;
{
u_32_t sum1, sum2, sumd, psum1, psum2, psumd, sumd1;
i6addr_t in;
struct icmp6_hdr *icmp6, *orgicmp;
int dlen;
udphdr_t *udp;
tcphdr_t *tcp;
nat_t *nat;
ip6_t *oip6;
if ((fin->fin_flx & (FI_SHORT|FI_FRAGBODY)))
return NULL;
if ((fin->fin_v != 6) || !(nat = nat6_icmperrorlookup(fin, dir)))
return NULL;
sumd1 = 0;
*nflags = IPN_ICMPERR;
icmp6 = fin->fin_dp;
oip6 = (ip6_t *)((char *)icmp6 + sizeof (*icmp6));
udp = (udphdr_t *)(((char *)oip6) + sizeof (*oip6));
tcp = (tcphdr_t *)udp;
dlen = fin->fin_plen - ((char *)udp - (char *)fin->fin_ip);
if (IP6_EQ((i6addr_t *)&oip6->ip6_dst, &nat->nat_oip6)) {
sum1 = LONG_SUM6((i6addr_t *)&oip6->ip6_src);
in = nat->nat_inip6;
oip6->ip6_src = in.in6;
} else {
sum1 = LONG_SUM6((i6addr_t *)&oip6->ip6_dst);
in = nat->nat_outip6;
oip6->ip6_dst = in.in6;
}
sum2 = LONG_SUM6(&in);
CALC_SUMD(sum1, sum2, sumd);
switch (oip6->ip6_nxt) {
case IPPROTO_TCP :
case IPPROTO_UDP :
psum1 = 0;
psum2 = 0;
psumd = 0;
if ((tcp->th_dport == nat->nat_oport) &&
(tcp->th_sport != nat->nat_inport)) {
psum1 = ntohs(tcp->th_sport);
psum2 = ntohs(nat->nat_inport);
tcp->th_sport = nat->nat_inport;
} else if ((tcp->th_sport == nat->nat_oport) &&
(tcp->th_dport != nat->nat_outport)) {
psum1 = ntohs(tcp->th_dport);
psum2 = ntohs(nat->nat_outport);
tcp->th_dport = nat->nat_outport;
}
if ((oip6->ip6_nxt == IPPROTO_TCP) && (dlen >= 18)) {
sum1 = ntohs(tcp->th_sum);
fix_datacksum(&tcp->th_sum, sumd);
sum2 = ntohs(tcp->th_sum);
CALC_SUMD(sum1, sum2, sumd);
sumd1 += sumd;
if (psum1 != psum2) {
CALC_SUMD(psum1, psum2, psumd);
fix_datacksum(&tcp->th_sum, psumd);
}
} else if ((oip6->ip6_nxt == IPPROTO_UDP) &&
(dlen >= 8) && (udp->uh_sum != 0)) {
sum1 = ntohs(udp->uh_sum);
fix_datacksum(&udp->uh_sum, sumd);
sum2 = ntohs(udp->uh_sum);
CALC_SUMD(sum1, sum2, sumd);
sumd1 += sumd;
if (psum1 != psum2) {
CALC_SUMD(psum1, psum2, psumd);
fix_datacksum(&udp->uh_sum, psumd);
}
} else {
CALC_SUMD(psum2, psum1, psumd);
sumd1 += psumd;
}
break;
case IPPROTO_ICMPV6 :
orgicmp = (struct icmp6_hdr *)udp;
if ((nat->nat_dir == NAT_OUTBOUND) &&
(orgicmp->icmp6_id != nat->nat_inport) &&
(dlen >= 8)) {
sum1 = ntohs(orgicmp->icmp6_id);
sum2 = ntohs(nat->nat_inport);
CALC_SUMD(sum1, sum2, sumd);
orgicmp->icmp6_id = nat->nat_inport;
fix_datacksum(&orgicmp->icmp6_cksum, sumd);
}
break;
default :
break;
}
if (sumd1 != 0) {
sumd1 = (sumd1 & 0xffff) + (sumd1 >> 16);
sumd1 = (sumd1 & 0xffff) + (sumd1 >> 16);
fix_incksum(&icmp6->icmp6_cksum, sumd1);
}
return nat;
}
nat_t *nat6_inlookup(fin, flags, p, src, mapdst)
fr_info_t *fin;
u_int flags, p;
struct in6_addr *src, *mapdst;
{
u_short sport, dport;
u_int sflags;
nat_t *nat;
int nflags;
i6addr_t dst;
void *ifp;
u_int hv;
ipf_stack_t *ifs = fin->fin_ifs;
if (fin != NULL)
ifp = fin->fin_ifp;
else
ifp = NULL;
sport = 0;
dport = 0;
dst.in6 = *mapdst;
sflags = flags & NAT_TCPUDPICMP;
switch (p)
{
case IPPROTO_TCP :
case IPPROTO_UDP :
sport = htons(fin->fin_data[0]);
dport = htons(fin->fin_data[1]);
break;
case IPPROTO_ICMPV6 :
if (flags & IPN_ICMPERR)
sport = fin->fin_data[1];
else
dport = fin->fin_data[1];
break;
default :
break;
}
if ((flags & SI_WILDP) != 0)
goto find_in_wild_ports;
hv = NAT_HASH_FN6(&dst, dport, 0xffffffff);
hv = NAT_HASH_FN6(src, hv + sport, ifs->ifs_ipf_nattable_sz);
nat = ifs->ifs_nat_table[1][hv];
for (; nat; nat = nat->nat_hnext[1]) {
if (nat->nat_v != 6)
continue;
if (nat->nat_ifps[0] != NULL) {
if ((ifp != NULL) && (ifp != nat->nat_ifps[0]))
continue;
} else if (ifp != NULL)
nat->nat_ifps[0] = ifp;
nflags = nat->nat_flags;
if (IP6_EQ(&nat->nat_oip6, src) &&
IP6_EQ(&nat->nat_outip6, &dst) &&
(((p == 0) &&
(sflags == (nat->nat_flags & IPN_TCPUDPICMP))) ||
(p == nat->nat_p))) {
switch (p)
{
#if 0
case IPPROTO_GRE :
if (nat->nat_call[1] != fin->fin_data[0])
continue;
break;
#endif
case IPPROTO_ICMPV6 :
if ((flags & IPN_ICMPERR) != 0) {
if (nat->nat_outport != sport)
continue;
} else {
if (nat->nat_outport != dport)
continue;
}
break;
case IPPROTO_TCP :
case IPPROTO_UDP :
if (nat->nat_oport != sport)
continue;
if (nat->nat_outport != dport)
continue;
break;
default :
break;
}
#ifdef IPF_V6_PROXIES
ipn = nat->nat_ptr;
if ((ipn != NULL) && (nat->nat_aps != NULL))
if (appr_match(fin, nat) != 0)
continue;
#endif
return nat;
}
}
find_in_wild_ports:
if (!(flags & NAT_TCPUDP) || !(flags & NAT_SEARCH))
return NULL;
if (ifs->ifs_nat_stats.ns_wilds == 0)
return NULL;
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
hv = NAT_HASH_FN6(&dst, 0, 0xffffffff);
hv = NAT_HASH_FN6(src, hv, ifs->ifs_ipf_nattable_sz);
WRITE_ENTER(&ifs->ifs_ipf_nat);
nat = ifs->ifs_nat_table[1][hv];
for (; nat; nat = nat->nat_hnext[1]) {
if (nat->nat_v != 6)
continue;
if (nat->nat_ifps[0] != NULL) {
if ((ifp != NULL) && (ifp != nat->nat_ifps[0]))
continue;
} else if (ifp != NULL)
nat->nat_ifps[0] = ifp;
if (nat->nat_p != fin->fin_p)
continue;
if (IP6_NEQ(&nat->nat_oip6, src) ||
IP6_NEQ(&nat->nat_outip6, &dst))
continue;
nflags = nat->nat_flags;
if (!(nflags & (NAT_TCPUDP|SI_WILDP)))
continue;
if (nat_wildok(nat, (int)sport, (int)dport, nflags,
NAT_INBOUND) == 1) {
if ((fin->fin_flx & FI_IGNORE) != 0)
break;
if ((nflags & SI_CLONE) != 0) {
nat = fr_natclone(fin, nat);
if (nat == NULL)
break;
} else {
MUTEX_ENTER(&ifs->ifs_ipf_nat_new);
ifs->ifs_nat_stats.ns_wilds--;
MUTEX_EXIT(&ifs->ifs_ipf_nat_new);
}
nat->nat_oport = sport;
nat->nat_outport = dport;
nat->nat_flags &= ~(SI_W_DPORT|SI_W_SPORT);
nat6_tabmove(nat, ifs);
break;
}
}
MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat);
return nat;
}
static void nat6_tabmove(nat, ifs)
nat_t *nat;
ipf_stack_t *ifs;
{
nat_t **natp;
u_int hv;
if (nat->nat_flags & SI_CLONE)
return;
if (nat->nat_hnext[0])
nat->nat_hnext[0]->nat_phnext[0] = nat->nat_phnext[0];
*nat->nat_phnext[0] = nat->nat_hnext[0];
ifs->ifs_nat_stats.ns_bucketlen[0][nat->nat_hv[0]]--;
if (nat->nat_hnext[1])
nat->nat_hnext[1]->nat_phnext[1] = nat->nat_phnext[1];
*nat->nat_phnext[1] = nat->nat_hnext[1];
ifs->ifs_nat_stats.ns_bucketlen[1][nat->nat_hv[1]]--;
hv = NAT_HASH_FN6(&nat->nat_inip6, nat->nat_inport, 0xffffffff);
hv = NAT_HASH_FN6(&nat->nat_oip6, hv + nat->nat_oport,
ifs->ifs_ipf_nattable_sz);
nat->nat_hv[0] = hv;
natp = &ifs->ifs_nat_table[0][hv];
if (*natp)
(*natp)->nat_phnext[0] = &nat->nat_hnext[0];
nat->nat_phnext[0] = natp;
nat->nat_hnext[0] = *natp;
*natp = nat;
ifs->ifs_nat_stats.ns_bucketlen[0][hv]++;
hv = NAT_HASH_FN6(&nat->nat_outip6, nat->nat_outport, 0xffffffff);
hv = NAT_HASH_FN6(&nat->nat_oip6, hv + nat->nat_oport,
ifs->ifs_ipf_nattable_sz);
nat->nat_hv[1] = hv;
natp = &ifs->ifs_nat_table[1][hv];
if (*natp)
(*natp)->nat_phnext[1] = &nat->nat_hnext[1];
nat->nat_phnext[1] = natp;
nat->nat_hnext[1] = *natp;
*natp = nat;
ifs->ifs_nat_stats.ns_bucketlen[1][hv]++;
}
nat_t *nat6_outlookup(fin, flags, p, src, dst)
fr_info_t *fin;
u_int flags, p;
struct in6_addr *src , *dst;
{
u_short sport, dport;
u_int sflags;
nat_t *nat;
int nflags;
void *ifp;
u_int hv;
ipf_stack_t *ifs = fin->fin_ifs;
ifp = fin->fin_ifp;
sflags = flags & IPN_TCPUDPICMP;
sport = 0;
dport = 0;
switch (p)
{
case IPPROTO_TCP :
case IPPROTO_UDP :
sport = htons(fin->fin_data[0]);
dport = htons(fin->fin_data[1]);
break;
case IPPROTO_ICMPV6 :
if (flags & IPN_ICMPERR)
sport = fin->fin_data[1];
else
dport = fin->fin_data[1];
break;
default :
break;
}
if ((flags & SI_WILDP) != 0)
goto find_out_wild_ports;
hv = NAT_HASH_FN6(src, sport, 0xffffffff);
hv = NAT_HASH_FN6(dst, hv + dport, ifs->ifs_ipf_nattable_sz);
nat = ifs->ifs_nat_table[0][hv];
for (; nat; nat = nat->nat_hnext[0]) {
if (nat->nat_v != 6)
continue;
if (nat->nat_ifps[1] != NULL) {
if ((ifp != NULL) && (ifp != nat->nat_ifps[1]))
continue;
} else if (ifp != NULL)
nat->nat_ifps[1] = ifp;
nflags = nat->nat_flags;
if (IP6_EQ(&nat->nat_inip6, src) &&
IP6_EQ(&nat->nat_oip6, dst) &&
(((p == 0) && (sflags == (nflags & NAT_TCPUDPICMP))) ||
(p == nat->nat_p))) {
switch (p)
{
#if 0
case IPPROTO_GRE :
if (nat->nat_call[1] != fin->fin_data[0])
continue;
break;
#endif
case IPPROTO_TCP :
case IPPROTO_UDP :
if (nat->nat_oport != dport)
continue;
if (nat->nat_inport != sport)
continue;
break;
default :
break;
}
#ifdef IPF_V6_PROXIES
ipn = nat->nat_ptr;
if ((ipn != NULL) && (nat->nat_aps != NULL))
if (appr_match(fin, nat) != 0)
continue;
#endif
return nat;
}
}
find_out_wild_ports:
if (!(flags & NAT_TCPUDP) || !(flags & NAT_SEARCH))
return NULL;
if (ifs->ifs_nat_stats.ns_wilds == 0)
return NULL;
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
hv = NAT_HASH_FN6(src, 0, 0xffffffff);
hv = NAT_HASH_FN6(dst, hv, ifs->ifs_ipf_nattable_sz);
WRITE_ENTER(&ifs->ifs_ipf_nat);
nat = ifs->ifs_nat_table[0][hv];
for (; nat; nat = nat->nat_hnext[0]) {
if (nat->nat_v != 6)
continue;
if (nat->nat_ifps[1] != NULL) {
if ((ifp != NULL) && (ifp != nat->nat_ifps[1]))
continue;
} else if (ifp != NULL)
nat->nat_ifps[1] = ifp;
if (nat->nat_p != fin->fin_p)
continue;
if (IP6_NEQ(&nat->nat_inip6, src) ||
IP6_NEQ(&nat->nat_oip6, dst))
continue;
nflags = nat->nat_flags;
if (!(nflags & (NAT_TCPUDP|SI_WILDP)))
continue;
if (nat_wildok(nat, (int)sport, (int)dport, nflags,
NAT_OUTBOUND) == 1) {
if ((fin->fin_flx & FI_IGNORE) != 0)
break;
if ((nflags & SI_CLONE) != 0) {
nat = fr_natclone(fin, nat);
if (nat == NULL)
break;
} else {
MUTEX_ENTER(&ifs->ifs_ipf_nat_new);
ifs->ifs_nat_stats.ns_wilds--;
MUTEX_EXIT(&ifs->ifs_ipf_nat_new);
}
nat->nat_inport = sport;
nat->nat_oport = dport;
if (nat->nat_outport == 0)
nat->nat_outport = sport;
nat->nat_flags &= ~(SI_W_DPORT|SI_W_SPORT);
nat6_tabmove(nat, ifs);
break;
}
}
MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat);
return nat;
}
nat_t *nat6_lookupredir(np, ifs)
natlookup_t *np;
ipf_stack_t *ifs;
{
fr_info_t fi;
nat_t *nat;
bzero((char *)&fi, sizeof (fi));
if (np->nl_flags & IPN_IN) {
fi.fin_data[0] = ntohs(np->nl_realport);
fi.fin_data[1] = ntohs(np->nl_outport);
} else {
fi.fin_data[0] = ntohs(np->nl_inport);
fi.fin_data[1] = ntohs(np->nl_outport);
}
if (np->nl_flags & IPN_TCP)
fi.fin_p = IPPROTO_TCP;
else if (np->nl_flags & IPN_UDP)
fi.fin_p = IPPROTO_UDP;
else if (np->nl_flags & (IPN_ICMPERR|IPN_ICMPQUERY))
fi.fin_p = IPPROTO_ICMPV6;
fi.fin_ifs = ifs;
if (np->nl_flags & IPN_IN) {
if ((nat = nat6_inlookup(&fi, np->nl_flags, fi.fin_p,
&np->nl_realip6, &np->nl_outip6))) {
np->nl_inipaddr = nat->nat_inip6;
np->nl_inport = nat->nat_inport;
}
} else {
if ((nat = nat6_outlookup(&fi, np->nl_flags, fi.fin_p,
&np->nl_inip6, &np->nl_outip6))) {
if ((np->nl_flags & IPN_FINDFORWARD) != 0) {
fr_info_t fin;
bzero((char *)&fin, sizeof (fin));
fin.fin_p = nat->nat_p;
fin.fin_data[0] = ntohs(nat->nat_outport);
fin.fin_data[1] = ntohs(nat->nat_oport);
fin.fin_ifs = ifs;
if (nat6_inlookup(&fin, np->nl_flags, fin.fin_p,
&nat->nat_outip6.in6,
&nat->nat_oip6.in6) != NULL) {
np->nl_flags &= ~IPN_FINDFORWARD;
}
}
np->nl_realip6 = nat->nat_outip6.in6;
np->nl_realport = nat->nat_outport;
}
}
return nat;
}
static int nat6_match(fin, np)
fr_info_t *fin;
ipnat_t *np;
{
frtuc_t *ft;
if (fin->fin_v != 6)
return 0;
if (np->in_p && fin->fin_p != np->in_p)
return 0;
if (fin->fin_out) {
if (!(np->in_redir & (NAT_MAP|NAT_MAPBLK)))
return 0;
if (IP6_MASKNEQ(&fin->fin_src6, &np->in_in[1], &np->in_in[0])
^ ((np->in_flags & IPN_NOTSRC) != 0))
return 0;
if (IP6_MASKNEQ(&fin->fin_dst6, &np->in_src[1], &np->in_src[0])
^ ((np->in_flags & IPN_NOTDST) != 0))
return 0;
} else {
if (!(np->in_redir & NAT_REDIRECT))
return 0;
if (IP6_MASKNEQ(&fin->fin_src6, &np->in_src[1], &np->in_src[0])
^ ((np->in_flags & IPN_NOTSRC) != 0))
return 0;
if (IP6_MASKNEQ(&fin->fin_dst6, &np->in_out[1], &np->in_out[0])
^ ((np->in_flags & IPN_NOTDST) != 0))
return 0;
}
ft = &np->in_tuc;
if (!(fin->fin_flx & FI_TCPUDP) ||
(fin->fin_flx & (FI_SHORT|FI_FRAGBODY))) {
if (ft->ftu_scmp || ft->ftu_dcmp)
return 0;
return 1;
}
return fr_tcpudpchk(fin, ft);
}
int fr_checknat6out(fin, passp)
fr_info_t *fin;
u_32_t *passp;
{
struct ifnet *ifp, *sifp;
int rval, natfailed;
ipnat_t *np = NULL;
u_int nflags = 0;
i6addr_t ipa, iph;
int natadd = 1;
frentry_t *fr;
nat_t *nat;
ipf_stack_t *ifs = fin->fin_ifs;
if (ifs->ifs_nat_stats.ns_rules == 0 || ifs->ifs_fr_nat_lock != 0)
return 0;
natfailed = 0;
fr = fin->fin_fr;
sifp = fin->fin_ifp;
if ((fr != NULL) && !(fr->fr_flags & FR_DUP) &&
fr->fr_tifs[fin->fin_rev].fd_ifp &&
fr->fr_tifs[fin->fin_rev].fd_ifp != (void *)-1)
fin->fin_ifp = fr->fr_tifs[fin->fin_rev].fd_ifp;
ifp = fin->fin_ifp;
if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) {
switch (fin->fin_p)
{
case IPPROTO_TCP :
nflags = IPN_TCP;
break;
case IPPROTO_UDP :
nflags = IPN_UDP;
break;
case IPPROTO_ICMPV6 :
if ((fin->fin_flx & FI_ICMPQUERY) != 0)
nflags = IPN_ICMPQUERY;
break;
default :
break;
}
#ifdef IPF_V6_PROXIES
if ((nflags & IPN_TCPUDP))
tcp = fin->fin_dp;
#endif
}
ipa = fin->fin_src6;
READ_ENTER(&ifs->ifs_ipf_nat);
if ((fin->fin_p == IPPROTO_ICMPV6) && !(nflags & IPN_ICMPQUERY) &&
(nat = nat6_icmperror(fin, &nflags, NAT_OUTBOUND)))
;
else if ((fin->fin_flx & FI_FRAG) && (nat = fr_nat_knownfrag(fin)))
natadd = 0;
else if ((nat = nat6_outlookup(fin, nflags|NAT_SEARCH,
(u_int)fin->fin_p, &fin->fin_src6.in6,
&fin->fin_dst6.in6))) {
nflags = nat->nat_flags;
} else {
u_32_t hv, nmsk;
i6addr_t msk;
int i;
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
i = 3;
msk.i6[0] = 0xffffffff;
msk.i6[1] = 0xffffffff;
msk.i6[2] = 0xffffffff;
msk.i6[3] = 0xffffffff;
nmsk = ifs->ifs_nat6_masks[3];
WRITE_ENTER(&ifs->ifs_ipf_nat);
maskloop:
IP6_AND(&ipa, &msk, &iph);
hv = NAT_HASH_FN6(&iph, 0, ifs->ifs_ipf_natrules_sz);
for (np = ifs->ifs_nat_rules[hv]; np; np = np->in_mnext)
{
if ((np->in_ifps[1] && (np->in_ifps[1] != ifp)))
continue;
if (np->in_v != 6)
continue;
if (np->in_p && (np->in_p != fin->fin_p))
continue;
if ((np->in_flags & IPN_RF) && !(np->in_flags & nflags))
continue;
if (np->in_flags & IPN_FILTER) {
if (!nat6_match(fin, np))
continue;
} else if (!IP6_MASKEQ(&ipa, &np->in_in[1],
&np->in_in[0]))
continue;
if ((fr != NULL) &&
!fr_matchtag(&np->in_tag, &fr->fr_nattag))
continue;
#ifdef IPF_V6_PROXIES
if (*np->in_plabel != '\0') {
if (((np->in_flags & IPN_FILTER) == 0) &&
(np->in_dport != tcp->th_dport))
continue;
if (appr_ok(fin, tcp, np) == 0)
continue;
}
#endif
if (nat = nat6_new(fin, np, NULL, nflags,
NAT_OUTBOUND)) {
np->in_hits++;
break;
} else
natfailed = -1;
}
if ((np == NULL) && (i >= 0)) {
while (i >= 0) {
while (nmsk) {
msk.i6[i] = htonl(ntohl(msk.i6[i])<<1);
if ((nmsk & 0x80000000) != 0) {
nmsk <<= 1;
goto maskloop;
}
nmsk <<= 1;
}
msk.i6[i--] = 0;
if (i >= 0) {
nmsk = ifs->ifs_nat6_masks[i];
if (nmsk != 0)
goto maskloop;
}
}
}
MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat);
}
if (nat != NULL) {
rval = fr_nat6out(fin, nat, natadd, nflags);
} else {
rval = natfailed;
}
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
if (rval == -1) {
if (passp != NULL)
*passp = FR_BLOCK;
fin->fin_flx |= FI_BADNAT;
}
fin->fin_ifp = sifp;
return rval;
}
int fr_nat6out(fin, nat, natadd, nflags)
fr_info_t *fin;
nat_t *nat;
int natadd;
u_32_t nflags;
{
struct icmp6_hdr *icmp6;
u_short *csump;
tcphdr_t *tcp;
ipnat_t *np;
int i;
ipf_stack_t *ifs = fin->fin_ifs;
#if defined(SOLARIS) && defined(_KERNEL)
net_handle_t net_data_p = ifs->ifs_ipf_ipv6;
#endif
tcp = NULL;
icmp6 = NULL;
csump = NULL;
np = nat->nat_ptr;
if ((natadd != 0) && (fin->fin_flx & FI_FRAG))
(void) fr_nat_newfrag(fin, 0, nat);
MUTEX_ENTER(&nat->nat_lock);
nat->nat_bytes[1] += fin->fin_plen;
nat->nat_pkts[1]++;
MUTEX_EXIT(&nat->nat_lock);
if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) {
if ((nat->nat_outport != 0) && (nflags & IPN_TCPUDP)) {
tcp = fin->fin_dp;
tcp->th_sport = nat->nat_outport;
fin->fin_data[0] = ntohs(nat->nat_outport);
}
if ((nat->nat_outport != 0) && (nflags & IPN_ICMPQUERY)) {
icmp6 = fin->fin_dp;
icmp6->icmp6_id = nat->nat_outport;
}
csump = nat_proto(fin, nat, nflags);
}
fin->fin_ip6->ip6_src = nat->nat_outip6.in6;
fin->fin_src6 = nat->nat_outip6;
nat_update(fin, nat, np);
if (csump != NULL && (!(nflags & IPN_TCPUDP) ||
!NET_IS_HCK_L4_FULL(net_data_p, fin->fin_m))) {
if (nflags & IPN_TCPUDP &&
NET_IS_HCK_L4_PART(net_data_p, fin->fin_m)) {
if (nat->nat_dir == NAT_OUTBOUND)
fix_outcksum(csump, nat->nat_sumd[1]);
else
fix_incksum(csump, nat->nat_sumd[1]);
} else {
if (nat->nat_dir == NAT_OUTBOUND)
fix_outcksum(csump, nat->nat_sumd[0]);
else
fix_incksum(csump, nat->nat_sumd[0]);
}
}
#ifdef IPFILTER_SYNC
ipfsync_update(SMC_NAT, fin, nat->nat_sync);
#endif
if ((np != NULL) && (np->in_apr != NULL)) {
i = appr_check(fin, nat);
if (i == 0)
i = 1;
} else
i = 1;
ATOMIC_INCL(ifs->ifs_nat_stats.ns_mapped[1]);
fin->fin_flx |= FI_NATED;
return i;
}
int fr_checknat6in(fin, passp)
fr_info_t *fin;
u_32_t *passp;
{
u_int nflags, natadd;
int rval, natfailed;
struct ifnet *ifp;
struct icmp6_hdr *icmp6;
tcphdr_t *tcp;
u_short dport;
ipnat_t *np;
nat_t *nat;
i6addr_t ipa, iph;
ipf_stack_t *ifs = fin->fin_ifs;
if (ifs->ifs_nat_stats.ns_rules == 0 || ifs->ifs_fr_nat_lock != 0)
return 0;
tcp = NULL;
icmp6 = NULL;
dport = 0;
natadd = 1;
nflags = 0;
natfailed = 0;
ifp = fin->fin_ifp;
if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) {
switch (fin->fin_p)
{
case IPPROTO_TCP :
nflags = IPN_TCP;
break;
case IPPROTO_UDP :
nflags = IPN_UDP;
break;
case IPPROTO_ICMPV6 :
icmp6 = fin->fin_dp;
if ((fin->fin_flx & FI_ICMPQUERY) != 0) {
nflags = IPN_ICMPQUERY;
dport = icmp6->icmp6_id;
} break;
default :
break;
}
if ((nflags & IPN_TCPUDP)) {
tcp = fin->fin_dp;
dport = tcp->th_dport;
}
}
ipa = fin->fin_dst6;
READ_ENTER(&ifs->ifs_ipf_nat);
if ((fin->fin_p == IPPROTO_ICMPV6) && !(nflags & IPN_ICMPQUERY) &&
(nat = nat6_icmperror(fin, &nflags, NAT_INBOUND)))
;
else if ((fin->fin_flx & FI_FRAG) && (nat = fr_nat_knownfrag(fin)))
natadd = 0;
else if ((nat = nat6_inlookup(fin, nflags|NAT_SEARCH, (u_int)fin->fin_p,
&fin->fin_src6.in6, &ipa.in6))) {
nflags = nat->nat_flags;
} else {
u_32_t hv, rmsk;
i6addr_t msk;
int i;
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
i = 3;
msk.i6[0] = 0xffffffff;
msk.i6[1] = 0xffffffff;
msk.i6[2] = 0xffffffff;
msk.i6[3] = 0xffffffff;
rmsk = ifs->ifs_rdr6_masks[3];
WRITE_ENTER(&ifs->ifs_ipf_nat);
maskloop:
IP6_AND(&ipa, &msk, &iph);
hv = NAT_HASH_FN6(&iph, 0, ifs->ifs_ipf_rdrrules_sz);
for (np = ifs->ifs_rdr_rules[hv]; np; np = np->in_rnext) {
if (np->in_ifps[0] && (np->in_ifps[0] != ifp))
continue;
if (np->in_v != fin->fin_v)
continue;
if (np->in_p && (np->in_p != fin->fin_p))
continue;
if ((np->in_flags & IPN_RF) && !(np->in_flags & nflags))
continue;
if (np->in_flags & IPN_FILTER) {
if (!nat6_match(fin, np))
continue;
} else {
if (!IP6_MASKEQ(&ipa, &np->in_out[1],
&np->in_out[0]))
continue;
if (np->in_pmin &&
((ntohs(np->in_pmax) < ntohs(dport)) ||
(ntohs(dport) < ntohs(np->in_pmin))))
continue;
}
#ifdef IPF_V6_PROXIES
if (*np->in_plabel != '\0') {
if (!appr_ok(fin, tcp, np)) {
continue;
}
}
#endif
nat = nat6_new(fin, np, NULL, nflags, NAT_INBOUND);
if (nat != NULL) {
np->in_hits++;
break;
} else
natfailed = -1;
}
if ((np == NULL) && (i >= 0)) {
while (i >= 0) {
while (rmsk) {
msk.i6[i] = htonl(ntohl(msk.i6[i])<<1);
if ((rmsk & 0x80000000) != 0) {
rmsk <<= 1;
goto maskloop;
}
rmsk <<= 1;
}
msk.i6[i--] = 0;
if (i >= 0) {
rmsk = ifs->ifs_rdr6_masks[i];
if (rmsk != 0)
goto maskloop;
}
}
}
MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat);
}
if (nat != NULL) {
rval = fr_nat6in(fin, nat, natadd, nflags);
} else {
rval = natfailed;
}
RWLOCK_EXIT(&ifs->ifs_ipf_nat);
if (rval == -1) {
if (passp != NULL)
*passp = FR_BLOCK;
fin->fin_flx |= FI_BADNAT;
}
return rval;
}
int fr_nat6in(fin, nat, natadd, nflags)
fr_info_t *fin;
nat_t *nat;
int natadd;
u_32_t nflags;
{
struct icmp6_hdr *icmp6;
u_short *csump;
tcphdr_t *tcp;
ipnat_t *np;
ipf_stack_t *ifs = fin->fin_ifs;
#if defined(SOLARIS) && defined(_KERNEL)
net_handle_t net_data_p = ifs->ifs_ipf_ipv6;
#endif
tcp = NULL;
csump = NULL;
np = nat->nat_ptr;
fin->fin_fr = nat->nat_fr;
if ((natadd != 0) && (fin->fin_flx & FI_FRAG))
(void) fr_nat_newfrag(fin, 0, nat);
#ifdef IPF_V6_PROXIES
if (np != NULL) {
if (np->in_apr != NULL) {
i = appr_check(fin, nat);
if (i == -1) {
return -1;
}
}
}
#endif
#ifdef IPFILTER_SYNC
ipfsync_update(SMC_NAT, fin, nat->nat_sync);
#endif
MUTEX_ENTER(&nat->nat_lock);
nat->nat_bytes[0] += fin->fin_plen;
nat->nat_pkts[0]++;
MUTEX_EXIT(&nat->nat_lock);
fin->fin_ip6->ip6_dst = nat->nat_inip6.in6;
fin->fin_dst6 = nat->nat_inip6;
if (nflags & IPN_TCPUDP)
tcp = fin->fin_dp;
if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) {
if ((nat->nat_inport != 0) && (nflags & IPN_TCPUDP)) {
tcp->th_dport = nat->nat_inport;
fin->fin_data[1] = ntohs(nat->nat_inport);
}
if ((nat->nat_inport != 0) && (nflags & IPN_ICMPQUERY)) {
icmp6 = fin->fin_dp;
icmp6->icmp6_id = nat->nat_inport;
}
csump = nat_proto(fin, nat, nflags);
}
nat_update(fin, nat, np);
if (csump != NULL) {
if (nat->nat_dir == NAT_OUTBOUND)
fix_incksum(csump, nat->nat_sumd[0]);
else
fix_outcksum(csump, nat->nat_sumd[0]);
}
#if defined(SOLARIS) && defined(_KERNEL)
if (nflags & IPN_TCPUDP &&
NET_IS_HCK_L4_PART(net_data_p, fin->fin_m)) {
csump = &fin->fin_m->b_datap->db_cksum16;
if (nat->nat_dir == NAT_OUTBOUND)
fix_outcksum(csump, nat->nat_sumd[1]);
else
fix_incksum(csump, nat->nat_sumd[1]);
}
#endif
ATOMIC_INCL(ifs->ifs_nat_stats.ns_mapped[0]);
fin->fin_flx |= FI_NATED;
if (np != NULL && np->in_tag.ipt_num[0] != 0)
fin->fin_nattag = &np->in_tag;
return 1;
}
static INLINE int nat_icmpquerytype6(icmptype)
int icmptype;
{
switch (icmptype)
{
case ICMP6_ECHO_REPLY:
case ICMP6_ECHO_REQUEST:
case ICMP6_MEMBERSHIP_QUERY:
case ICMP6_MEMBERSHIP_REPORT:
case ICMP6_MEMBERSHIP_REDUCTION:
case ICMP6_WRUREQUEST:
case ICMP6_WRUREPLY:
case MLD6_MTRACE_RESP:
case MLD6_MTRACE:
return 1;
default:
return 0;
}
}