| /* |
| * Copyright (C) 2012 by Darren Reed. |
| * |
| * See the IPFILTER.LICENCE file for details on licencing. |
| */ |
| #if defined(KERNEL) || defined(_KERNEL) |
| # undef KERNEL |
| # undef ipf_nat6_KERNEL |
| # define KERNEL 1 |
| # define ipf_nat6_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(_KERNEL) && defined(__NetBSD_Version__) && \ |
| (__NetBSD_Version__ >= 399002000) |
| # include <sys/kauth.h> |
| #endif |
| #if !defined(_KERNEL) |
| # include <stdio.h> |
| # include <string.h> |
| # include <stdlib.h> |
| # define ipf_nat6_KERNEL |
| # ifdef ipf_nat6__OpenBSD__ |
| struct file; |
| # endif |
| # include <sys/uio.h> |
| # undef ipf_nat6_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 ipf_nat6_KERNEL |
| # include <sys/dditypes.h> |
| # endif |
| # include <sys/stream.h> |
| # include <sys/kmem.h> |
| #endif |
| #if ipf_nat6__FreeBSD_version >= 300000 |
| # include <sys/queue.h> |
| #endif |
| #include <net/if.h> |
| #if ipf_nat6__FreeBSD_version >= 300000 |
| # include <net/if_var.h> |
| #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/ip_lookup.h" |
| #include "netinet/ip_dstlist.h" |
| #include "netinet/ip_sync.h" |
| #if (__FreeBSD_version >= 300000) |
| # include <sys/malloc.h> |
| #endif |
| #ifdef HAS_SYS_MD5_H |
| # include <sys/md5.h> |
| #else |
| # include "md5.h" |
| #endif |
| /* END OF INCLUDES */ |
| |
| #undef SOCKADDR_IN |
| #define SOCKADDR_IN struct sockaddr_in |
| |
| #if !defined(lint) |
| static const char rcsid[] = "@(#)$Id$"; |
| #endif |
| |
| #ifdef USE_INET6 |
| static struct hostmap *ipf_nat6_hostmap __P((ipf_nat_softc_t *, ipnat_t *, |
| i6addr_t *, i6addr_t *, |
| i6addr_t *, u_32_t)); |
| static int ipf_nat6_match __P((fr_info_t *, ipnat_t *)); |
| static void ipf_nat6_tabmove __P((ipf_nat_softc_t *, nat_t *)); |
| static int ipf_nat6_decap __P((fr_info_t *, nat_t *)); |
| static int ipf_nat6_nextaddr __P((fr_info_t *, nat_addr_t *, i6addr_t *, |
| i6addr_t *)); |
| static int ipf_nat6_icmpquerytype __P((int)); |
| static int ipf_nat6_out __P((fr_info_t *, nat_t *, int, u_32_t)); |
| static int ipf_nat6_in __P((fr_info_t *, nat_t *, int, u_32_t)); |
| static int ipf_nat6_builddivertmp __P((ipf_nat_softc_t *, ipnat_t *)); |
| static int ipf_nat6_nextaddrinit __P((ipf_main_softc_t *, char *, |
| nat_addr_t *, int, void *)); |
| static int ipf_nat6_insert __P((ipf_main_softc_t *, ipf_nat_softc_t *, |
| nat_t *)); |
| |
| |
| #define NINCLSIDE6(y,x) ATOMIC_INCL(softn->ipf_nat_stats.ns_side6[y].x) |
| #define NBUMPSIDE(y,x) softn->ipf_nat_stats.ns_side[y].x++ |
| #define NBUMPSIDE6(y,x) softn->ipf_nat_stats.ns_side6[y].x++ |
| #define NBUMPSIDE6D(y,x) \ |
| do { \ |
| softn->ipf_nat_stats.ns_side6[y].x++; \ |
| DT(x); \ |
| _NOTE(CONSTCOND) \ |
| } while (0) |
| #define NBUMPSIDE6DX(y,x,z) \ |
| do { \ |
| softn->ipf_nat_stats.ns_side6[y].x++; \ |
| DT(z); \ |
| _NOTE(CONSTCOND) \ |
| } while (0) |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_ruleaddrinit */ |
| /* Returns: int - 0 == success, else failure */ |
| /* Parameters: in(I) - NAT rule that requires address fields to be init'd */ |
| /* */ |
| /* For each of the source/destination address fields in a NAT rule, call */ |
| /* ipf_nat6_nextaddrinit() to prepare the structure for active duty. Other */ |
| /* IPv6 specific actions can also be taken care of here. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_ruleaddrinit(softc, softn, n) |
| ipf_main_softc_t *softc; |
| ipf_nat_softc_t *softn; |
| ipnat_t *n; |
| { |
| int idx, error; |
| |
| if (n->in_redir == NAT_BIMAP) { |
| n->in_ndstip6 = n->in_osrcip6; |
| n->in_ndstmsk6 = n->in_osrcmsk6; |
| n->in_odstip6 = n->in_nsrcip6; |
| n->in_odstmsk6 = n->in_nsrcmsk6; |
| |
| } |
| |
| if (n->in_redir & NAT_REDIRECT) |
| idx = 1; |
| else |
| idx = 0; |
| /* |
| * Initialise all of the address fields. |
| */ |
| error = ipf_nat6_nextaddrinit(softc, n->in_names, &n->in_osrc, 1, |
| n->in_ifps[idx]); |
| if (error != 0) |
| return error; |
| |
| error = ipf_nat6_nextaddrinit(softc, n->in_names, &n->in_odst, 1, |
| n->in_ifps[idx]); |
| if (error != 0) |
| return error; |
| |
| error = ipf_nat6_nextaddrinit(softc, n->in_names, &n->in_nsrc, 1, |
| n->in_ifps[idx]); |
| if (error != 0) |
| return error; |
| |
| error = ipf_nat6_nextaddrinit(softc, n->in_names, &n->in_ndst, 1, |
| n->in_ifps[idx]); |
| if (error != 0) |
| return error; |
| |
| if (n->in_redir & NAT_DIVERTUDP) { |
| if (ipf_nat6_builddivertmp(softn, n) != 0) |
| return ENOBUFS; |
| } |
| return 0; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_addrdr */ |
| /* Returns: Nil */ |
| /* Parameters: n(I) - pointer to NAT rule to add */ |
| /* */ |
| /* Adds a redirect rule to the hash table of redirect rules and the list of */ |
| /* loaded NAT rules. Updates the bitmask indicating which netmasks are in */ |
| /* use by redirect rules. */ |
| /* ------------------------------------------------------------------------ */ |
| void |
| ipf_nat6_addrdr(softn, n) |
| ipf_nat_softc_t *softn; |
| ipnat_t *n; |
| { |
| i6addr_t *mask; |
| ipnat_t **np; |
| i6addr_t j; |
| u_int hv; |
| int k; |
| |
| if ((n->in_redir & NAT_BIMAP) == NAT_BIMAP) { |
| k = count6bits(n->in_nsrcmsk6.i6); |
| mask = &n->in_nsrcmsk6; |
| IP6_AND(&n->in_odstip6, &n->in_odstmsk6, &j); |
| hv = NAT_HASH_FN6(&j, 0, softn->ipf_nat_rdrrules_sz); |
| |
| } else if (n->in_odstatype == FRI_NORMAL) { |
| k = count6bits(n->in_odstmsk6.i6); |
| mask = &n->in_odstmsk6; |
| IP6_AND(&n->in_odstip6, &n->in_odstmsk6, &j); |
| hv = NAT_HASH_FN6(&j, 0, softn->ipf_nat_rdrrules_sz); |
| } else { |
| k = 0; |
| hv = 0; |
| mask = NULL; |
| } |
| ipf_inet6_mask_add(k, mask, &softn->ipf_nat6_rdr_mask); |
| |
| np = softn->ipf_nat_rdr_rules + hv; |
| while (*np != NULL) |
| np = &(*np)->in_rnext; |
| n->in_rnext = NULL; |
| n->in_prnext = np; |
| n->in_hv[0] = hv; |
| n->in_use++; |
| *np = n; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_addmap */ |
| /* Returns: Nil */ |
| /* Parameters: n(I) - pointer to NAT rule to add */ |
| /* */ |
| /* Adds a NAT map rule to the hash table of rules and the list of loaded */ |
| /* NAT rules. Updates the bitmask indicating which netmasks are in use by */ |
| /* redirect rules. */ |
| /* ------------------------------------------------------------------------ */ |
| void |
| ipf_nat6_addmap(softn, n) |
| ipf_nat_softc_t *softn; |
| ipnat_t *n; |
| { |
| i6addr_t *mask; |
| ipnat_t **np; |
| i6addr_t j; |
| u_int hv; |
| int k; |
| |
| if (n->in_osrcatype == FRI_NORMAL) { |
| k = count6bits(n->in_osrcmsk6.i6); |
| mask = &n->in_osrcmsk6; |
| IP6_AND(&n->in_osrcip6, &n->in_osrcmsk6, &j); |
| hv = NAT_HASH_FN6(&j, 0, softn->ipf_nat_maprules_sz); |
| } else { |
| k = 0; |
| hv = 0; |
| mask = NULL; |
| } |
| ipf_inet6_mask_add(k, mask, &softn->ipf_nat6_map_mask); |
| |
| np = softn->ipf_nat_map_rules + hv; |
| while (*np != NULL) |
| np = &(*np)->in_mnext; |
| n->in_mnext = NULL; |
| n->in_pmnext = np; |
| n->in_hv[1] = hv; |
| n->in_use++; |
| *np = n; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_del_rdr */ |
| /* Returns: Nil */ |
| /* Parameters: n(I) - pointer to NAT rule to delete */ |
| /* */ |
| /* Removes a NAT rdr rule from the hash table of NAT rdr rules. */ |
| /* ------------------------------------------------------------------------ */ |
| void |
| ipf_nat6_delrdr(softn, n) |
| ipf_nat_softc_t *softn; |
| ipnat_t *n; |
| { |
| i6addr_t *mask; |
| int k; |
| |
| if ((n->in_redir & NAT_BIMAP) == NAT_BIMAP) { |
| k = count6bits(n->in_nsrcmsk6.i6); |
| mask = &n->in_nsrcmsk6; |
| } else if (n->in_odstatype == FRI_NORMAL) { |
| k = count6bits(n->in_odstmsk6.i6); |
| mask = &n->in_odstmsk6; |
| } else { |
| k = 0; |
| mask = NULL; |
| } |
| ipf_inet6_mask_del(k, mask, &softn->ipf_nat6_rdr_mask); |
| |
| if (n->in_rnext != NULL) |
| n->in_rnext->in_prnext = n->in_prnext; |
| *n->in_prnext = n->in_rnext; |
| n->in_use--; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_delmap */ |
| /* Returns: Nil */ |
| /* Parameters: n(I) - pointer to NAT rule to delete */ |
| /* */ |
| /* Removes a NAT map rule from the hash table of NAT map rules. */ |
| /* ------------------------------------------------------------------------ */ |
| void |
| ipf_nat6_delmap(softn, n) |
| ipf_nat_softc_t *softn; |
| ipnat_t *n; |
| { |
| i6addr_t *mask; |
| int k; |
| |
| if (n->in_osrcatype == FRI_NORMAL) { |
| k = count6bits(n->in_osrcmsk6.i6); |
| mask = &n->in_osrcmsk6; |
| } else { |
| k = 0; |
| mask = NULL; |
| } |
| ipf_inet6_mask_del(k, mask, &softn->ipf_nat6_map_mask); |
| |
| if (n->in_mnext != NULL) |
| n->in_mnext->in_pmnext = n->in_pmnext; |
| *n->in_pmnext = n->in_mnext; |
| n->in_use--; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_hostmap */ |
| /* Returns: struct hostmap* - NULL if no hostmap could be created, */ |
| /* else a pointer to the hostmapping to use */ |
| /* Parameters: np(I) - pointer to NAT rule */ |
| /* real(I) - real IP address */ |
| /* map(I) - mapped IP address */ |
| /* port(I) - destination port number */ |
| /* Write Locks: ipf_nat */ |
| /* */ |
| /* Check if an ip address has already been allocated for a given mapping */ |
| /* that is not doing port based translation. If is not yet allocated, then */ |
| /* create a new entry if a non-NULL NAT rule pointer has been supplied. */ |
| /* ------------------------------------------------------------------------ */ |
| static struct hostmap * |
| ipf_nat6_hostmap(softn, np, src, dst, map, port) |
| ipf_nat_softc_t *softn; |
| ipnat_t *np; |
| i6addr_t *src, *dst, *map; |
| u_32_t port; |
| { |
| 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 = softn->ipf_hm_maptable[hv]; hm; hm = hm->hm_next) |
| if (IP6_EQ(&hm->hm_osrc6, src) && |
| IP6_EQ(&hm->hm_odst6, dst) && |
| ((np == NULL) || (np == hm->hm_ipnat)) && |
| ((port == 0) || (port == hm->hm_port))) { |
| softn->ipf_nat_stats.ns_hm_addref++; |
| hm->hm_ref++; |
| return hm; |
| } |
| |
| if (np == NULL) { |
| softn->ipf_nat_stats.ns_hm_nullnp++; |
| return NULL; |
| } |
| |
| KMALLOC(hm, hostmap_t *); |
| if (hm) { |
| hm->hm_next = softn->ipf_hm_maplist; |
| hm->hm_pnext = &softn->ipf_hm_maplist; |
| if (softn->ipf_hm_maplist != NULL) |
| softn->ipf_hm_maplist->hm_pnext = &hm->hm_next; |
| softn->ipf_hm_maplist = hm; |
| hm->hm_hnext = softn->ipf_hm_maptable[hv]; |
| hm->hm_phnext = softn->ipf_hm_maptable + hv; |
| if (softn->ipf_hm_maptable[hv] != NULL) |
| softn->ipf_hm_maptable[hv]->hm_phnext = &hm->hm_hnext; |
| softn->ipf_hm_maptable[hv] = hm; |
| hm->hm_ipnat = np; |
| np->in_use++; |
| hm->hm_osrcip6 = *src; |
| hm->hm_odstip6 = *dst; |
| hm->hm_nsrcip6 = *map; |
| hm->hm_ndstip6.i6[0] = 0; |
| hm->hm_ndstip6.i6[1] = 0; |
| hm->hm_ndstip6.i6[2] = 0; |
| hm->hm_ndstip6.i6[3] = 0; |
| hm->hm_ref = 1; |
| hm->hm_port = port; |
| hm->hm_hv = hv; |
| hm->hm_v = 6; |
| softn->ipf_nat_stats.ns_hm_new++; |
| } else { |
| softn->ipf_nat_stats.ns_hm_newfail++; |
| } |
| return hm; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_newmap */ |
| /* Returns: int - -1 == error, 0 == success */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT entry */ |
| /* ni(I) - pointer to structure with misc. information needed */ |
| /* to create new NAT entry. */ |
| /* */ |
| /* Given an empty NAT structure, populate it with new information about a */ |
| /* new NAT session, as defined by the matching NAT rule. */ |
| /* ni.nai_ip is passed in uninitialised and must be set, in host byte order,*/ |
| /* to the new IP address for the translation. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_newmap(fin, nat, ni) |
| fr_info_t *fin; |
| nat_t *nat; |
| natinfo_t *ni; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| 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; |
| |
| /* |
| * If it's an outbound packet which doesn't match any existing |
| * record, then create a new port |
| */ |
| l = 0; |
| hm = NULL; |
| np = ni->nai_np; |
| st_ip = np->in_snip6; |
| st_port = np->in_spnext; |
| flags = nat->nat_flags; |
| |
| if (flags & IPN_ICMPQUERY) { |
| sport = fin->fin_data[1]; |
| dport = 0; |
| } else { |
| sport = htons(fin->fin_data[0]); |
| dport = htons(fin->fin_data[1]); |
| } |
| |
| /* |
| * Do a loop until we either run out of entries to try or we find |
| * a NAT mapping that isn't currently being used. This is done |
| * because the change to the source is not (usually) being fixed. |
| */ |
| do { |
| port = 0; |
| in = np->in_nsrc.na_nextaddr; |
| if (l == 0) { |
| /* |
| * Check to see if there is an existing NAT |
| * setup for this IP address pair. |
| */ |
| hm = ipf_nat6_hostmap(softn, np, &fin->fin_src6, |
| &fin->fin_dst6, &in, 0); |
| if (hm != NULL) |
| in = hm->hm_nsrcip6; |
| } else if ((l == 1) && (hm != NULL)) { |
| ipf_nat_hostmapdel(softc, &hm); |
| } |
| |
| nat->nat_hm = hm; |
| |
| if (IP6_ISONES(&np->in_nsrcmsk6) && (np->in_spnext == 0)) { |
| if (l > 0) { |
| NBUMPSIDE6DX(1, ns_exhausted, ns_exhausted_1); |
| return -1; |
| } |
| } |
| |
| if ((np->in_redir == NAT_BIMAP) && |
| IP6_EQ(&np->in_osrcmsk6, &np->in_nsrcmsk6)) { |
| i6addr_t temp; |
| /* |
| * map the address block in a 1:1 fashion |
| */ |
| temp.i6[0] = fin->fin_src6.i6[0] & |
| ~np->in_osrcmsk6.i6[0]; |
| temp.i6[1] = fin->fin_src6.i6[1] & |
| ~np->in_osrcmsk6.i6[1]; |
| temp.i6[2] = fin->fin_src6.i6[2] & |
| ~np->in_osrcmsk6.i6[0]; |
| temp.i6[3] = fin->fin_src6.i6[3] & |
| ~np->in_osrcmsk6.i6[3]; |
| in = np->in_nsrcip6; |
| IP6_MERGE(&in, &temp, &np->in_osrc); |
| |
| #ifdef NEED_128BIT_MATH |
| } else if (np->in_redir & NAT_MAPBLK) { |
| if ((l >= np->in_ppip) || ((l > 0) && |
| !(flags & IPN_TCPUDP))) { |
| NBUMPSIDE6DX(1, ns_exhausted, ns_exhausted_2); |
| return -1; |
| } |
| /* |
| * map-block - Calculate destination address. |
| */ |
| IP6_MASK(&in, &fin->fin_src6, &np->in_osrcmsk6); |
| in = ntohl(in); |
| inb = in; |
| in.s_addr /= np->in_ippip; |
| in.s_addr &= ntohl(~np->in_nsrcmsk6); |
| in.s_addr += ntohl(np->in_nsrcaddr6); |
| /* |
| * Calculate destination port. |
| */ |
| 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_nsrcaddr) && |
| IP6_ISONES(&np->in_nsrcmsk)) { |
| /* |
| * 0/32 - use the interface's IP address. |
| */ |
| if ((l > 0) || |
| ipf_ifpaddr(softc, 6, FRI_NORMAL, fin->fin_ifp, |
| &in, NULL) == -1) { |
| NBUMPSIDE6DX(1, ns_new_ifpaddr, |
| ns_new_ifpaddr_1); |
| return -1; |
| } |
| |
| } else if (IP6_ISZERO(&np->in_nsrcip6) && |
| IP6_ISZERO(&np->in_nsrcmsk6)) { |
| /* |
| * 0/0 - use the original source address/port. |
| */ |
| if (l > 0) { |
| NBUMPSIDE6DX(1, ns_exhausted, ns_exhausted_3); |
| return -1; |
| } |
| in = fin->fin_src6; |
| |
| } else if (!IP6_ISONES(&np->in_nsrcmsk6) && |
| (np->in_spnext == 0) && ((l > 0) || (hm == NULL))) { |
| IP6_INC(&np->in_snip6); |
| } |
| |
| natl = NULL; |
| |
| if ((flags & IPN_TCPUDP) && |
| ((np->in_redir & NAT_MAPBLK) == 0) && |
| (np->in_flags & IPN_AUTOPORTMAP)) { |
| #ifdef NEED_128BIT_MATH |
| /* |
| * "ports auto" (without map-block) |
| */ |
| if ((l > 0) && (l % np->in_ppip == 0)) { |
| if ((l > np->in_ppip) && |
| !IP6_ISONES(&np->in_nsrcmsk)) { |
| IP6_INC(&np->in_snip6) |
| } |
| } |
| 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); |
| } |
| #else |
| port = port; |
| #endif |
| |
| } else if (((np->in_redir & NAT_MAPBLK) == 0) && |
| (flags & IPN_TCPUDPICMP) && (np->in_spnext != 0)) { |
| /* |
| * Standard port translation. Select next port. |
| */ |
| if (np->in_flags & IPN_SEQUENTIAL) { |
| port = np->in_spnext; |
| } else { |
| port = ipf_random() % (np->in_spmax - |
| np->in_spmin + 1); |
| port += np->in_spmin; |
| } |
| port = htons(port); |
| np->in_spnext++; |
| |
| if (np->in_spnext > np->in_spmax) { |
| np->in_spnext = np->in_spmin; |
| if (!IP6_ISONES(&np->in_nsrcmsk6)) { |
| IP6_INC(&np->in_snip6); |
| } |
| } |
| } |
| |
| if (np->in_flags & IPN_SIPRANGE) { |
| if (IP6_GT(&np->in_snip, &np->in_nsrcmsk)) |
| np->in_snip6 = np->in_nsrcip6; |
| } else { |
| i6addr_t a1, a2; |
| |
| a1 = np->in_snip6; |
| IP6_INC(&a1); |
| IP6_AND(&a1, &np->in_nsrcmsk6, &a2); |
| |
| if (!IP6_ISONES(&np->in_nsrcmsk6) && |
| IP6_GT(&a2, &np->in_nsrcip6)) { |
| IP6_ADD(&np->in_nsrcip6, 1, &np->in_snip6); |
| } |
| } |
| |
| if ((port == 0) && (flags & IPN_TCPUDPICMP)) |
| port = sport; |
| |
| /* |
| * Here we do a lookup of the connection as seen from |
| * the outside. If an IP# pair already exists, try |
| * again. So if you have A->B becomes C->B, you can |
| * also have D->E become C->E but not D->B causing |
| * another C->B. Also take protocol and ports into |
| * account when determining whether a pre-existing |
| * NAT setup will cause an external conflict where |
| * this is appropriate. |
| */ |
| sp = fin->fin_data[0]; |
| dp = fin->fin_data[1]; |
| fin->fin_data[0] = fin->fin_data[1]; |
| fin->fin_data[1] = ntohs(port); |
| natl = ipf_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; |
| |
| /* |
| * Has the search wrapped around and come back to the |
| * start ? |
| */ |
| if ((natl != NULL) && |
| (np->in_spnext != 0) && (st_port == np->in_spnext) && |
| (!IP6_ISZERO(&np->in_snip6) && |
| IP6_EQ(&st_ip, &np->in_snip6))) { |
| NBUMPSIDE6D(1, ns_wrap); |
| return -1; |
| } |
| l++; |
| } while (natl != NULL); |
| |
| /* Setup the NAT table */ |
| nat->nat_osrc6 = fin->fin_src6; |
| nat->nat_nsrc6 = in; |
| nat->nat_odst6 = fin->fin_dst6; |
| nat->nat_ndst6 = fin->fin_dst6; |
| if (nat->nat_hm == NULL) |
| nat->nat_hm = ipf_nat6_hostmap(softn, np, &fin->fin_src6, |
| &fin->fin_dst6, |
| &nat->nat_nsrc6, 0); |
| |
| if (flags & IPN_TCPUDP) { |
| nat->nat_osport = sport; |
| nat->nat_nsport = port; /* sport */ |
| nat->nat_odport = dport; |
| nat->nat_ndport = dport; |
| ((tcphdr_t *)fin->fin_dp)->th_sport = port; |
| } else if (flags & IPN_ICMPQUERY) { |
| nat->nat_oicmpid = fin->fin_data[1]; |
| ((struct icmp6_hdr *)fin->fin_dp)->icmp6_id = port; |
| nat->nat_nicmpid = port; |
| } |
| return 0; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_newrdr */ |
| /* Returns: int - -1 == error, 0 == success (no move), 1 == success and */ |
| /* allow rule to be moved if IPN_ROUNDR is set. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT entry */ |
| /* ni(I) - pointer to structure with misc. information needed */ |
| /* to create new NAT entry. */ |
| /* */ |
| /* ni.nai_ip is passed in uninitialised and must be set, in host byte order,*/ |
| /* to the new IP address for the translation. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_newrdr(fin, nat, ni) |
| fr_info_t *fin; |
| nat_t *nat; |
| natinfo_t *ni; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| u_short nport, dport, sport; |
| u_short sp, dp; |
| hostmap_t *hm; |
| u_32_t flags; |
| i6addr_t in; |
| ipnat_t *np; |
| nat_t *natl; |
| int move; |
| |
| 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 = nat->nat_flags; |
| |
| if (flags & IPN_ICMPQUERY) { |
| dport = fin->fin_data[1]; |
| sport = 0; |
| } else { |
| sport = htons(fin->fin_data[0]); |
| dport = htons(fin->fin_data[1]); |
| } |
| |
| /* TRACE sport, dport */ |
| |
| |
| /* |
| * If the matching rule has IPN_STICKY set, then we want to have the |
| * same rule kick in as before. Why would this happen? If you have |
| * a collection of rdr rules with "round-robin sticky", the current |
| * packet might match a different one to the previous connection but |
| * we want the same destination to be used. |
| */ |
| if (((np->in_flags & (IPN_ROUNDR|IPN_SPLIT)) != 0) && |
| ((np->in_flags & IPN_STICKY) != 0)) { |
| hm = ipf_nat6_hostmap(softn, NULL, &fin->fin_src6, |
| &fin->fin_dst6, &in, (u_32_t)dport); |
| if (hm != NULL) { |
| in = hm->hm_ndstip6; |
| np = hm->hm_ipnat; |
| ni->nai_np = np; |
| move = 0; |
| } |
| } |
| |
| /* |
| * Otherwise, it's an inbound packet. Most likely, we don't |
| * want to rewrite source ports and source addresses. Instead, |
| * we want to rewrite to a fixed internal address and fixed |
| * internal port. |
| */ |
| if (np->in_flags & IPN_SPLIT) { |
| in = np->in_dnip6; |
| |
| if ((np->in_flags & (IPN_ROUNDR|IPN_STICKY)) == IPN_STICKY) { |
| hm = ipf_nat6_hostmap(softn, NULL, &fin->fin_src6, |
| &fin->fin_dst6, &in, |
| (u_32_t)dport); |
| if (hm != NULL) { |
| in = hm->hm_ndstip6; |
| move = 0; |
| } |
| } |
| |
| if (hm == NULL || hm->hm_ref == 1) { |
| if (IP6_EQ(&np->in_ndstip6, &in)) { |
| np->in_dnip6 = np->in_ndstmsk6; |
| move = 0; |
| } else { |
| np->in_dnip6 = np->in_ndstip6; |
| } |
| } |
| |
| } else if (IP6_ISZERO(&np->in_ndstaddr) && |
| IP6_ISONES(&np->in_ndstmsk)) { |
| /* |
| * 0/32 - use the interface's IP address. |
| */ |
| if (ipf_ifpaddr(softc, 6, FRI_NORMAL, fin->fin_ifp, |
| &in, NULL) == -1) { |
| NBUMPSIDE6DX(0, ns_new_ifpaddr, ns_new_ifpaddr_2); |
| return -1; |
| } |
| |
| } else if (IP6_ISZERO(&np->in_ndstip6) && |
| IP6_ISZERO(&np->in_ndstmsk6)) { |
| /* |
| * 0/0 - use the original destination address/port. |
| */ |
| in = fin->fin_dst6; |
| |
| } else if (np->in_redir == NAT_BIMAP && |
| IP6_EQ(&np->in_ndstmsk6, &np->in_odstmsk6)) { |
| i6addr_t temp; |
| /* |
| * map the address block in a 1:1 fashion |
| */ |
| temp.i6[0] = fin->fin_dst6.i6[0] & ~np->in_osrcmsk6.i6[0]; |
| temp.i6[1] = fin->fin_dst6.i6[1] & ~np->in_osrcmsk6.i6[1]; |
| temp.i6[2] = fin->fin_dst6.i6[2] & ~np->in_osrcmsk6.i6[0]; |
| temp.i6[3] = fin->fin_dst6.i6[3] & ~np->in_osrcmsk6.i6[3]; |
| in = np->in_ndstip6; |
| IP6_MERGE(&in, &temp, &np->in_ndstmsk6); |
| } else { |
| in = np->in_ndstip6; |
| } |
| |
| if ((np->in_dpnext == 0) || ((flags & NAT_NOTRULEPORT) != 0)) |
| nport = dport; |
| else { |
| /* |
| * Whilst not optimized for the case where |
| * pmin == pmax, the gain is not significant. |
| */ |
| if (((np->in_flags & IPN_FIXEDDPORT) == 0) && |
| (np->in_odport != np->in_dtop)) { |
| nport = ntohs(dport) - np->in_odport + np->in_dpmax; |
| nport = htons(nport); |
| } else { |
| nport = htons(np->in_dpnext); |
| np->in_dpnext++; |
| if (np->in_dpnext > np->in_dpmax) |
| np->in_dpnext = np->in_dpmin; |
| } |
| } |
| |
| /* |
| * When the redirect-to address is set to 0.0.0.0, just |
| * assume a blank `forwarding' of the packet. We don't |
| * setup any translation for this either. |
| */ |
| if (IP6_ISZERO(&in)) { |
| if (nport == dport) { |
| NBUMPSIDE6D(0, ns_xlate_null); |
| return -1; |
| } |
| in = fin->fin_dst6; |
| } |
| |
| /* |
| * Check to see if this redirect mapping already exists and if |
| * it does, return "failure" (allowing it to be created will just |
| * cause one or both of these "connections" to stop working.) |
| */ |
| 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 = ipf_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) { |
| NBUMPSIDE6D(0, ns_xlate_exists); |
| return -1; |
| } |
| |
| nat->nat_ndst6 = in; |
| nat->nat_odst6 = fin->fin_dst6; |
| nat->nat_nsrc6 = fin->fin_src6; |
| nat->nat_osrc6 = fin->fin_src6; |
| if ((nat->nat_hm == NULL) && ((np->in_flags & IPN_STICKY) != 0)) |
| nat->nat_hm = ipf_nat6_hostmap(softn, np, &fin->fin_src6, |
| &fin->fin_dst6, &in, |
| (u_32_t)dport); |
| |
| if (flags & IPN_TCPUDP) { |
| nat->nat_odport = dport; |
| nat->nat_ndport = nport; |
| nat->nat_osport = sport; |
| nat->nat_nsport = sport; |
| ((tcphdr_t *)fin->fin_dp)->th_dport = nport; |
| } else if (flags & IPN_ICMPQUERY) { |
| nat->nat_oicmpid = fin->fin_data[1]; |
| ((struct icmp6_hdr *)fin->fin_dp)->icmp6_id = nport; |
| nat->nat_nicmpid = nport; |
| } |
| |
| return move; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_add */ |
| /* Returns: nat6_t* - NULL == failure to create new NAT structure, */ |
| /* else pointer to new NAT structure */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* np(I) - pointer to NAT rule */ |
| /* natsave(I) - pointer to where to store NAT struct pointer */ |
| /* flags(I) - flags describing the current packet */ |
| /* direction(I) - direction of packet (in/out) */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* Attempts to create a new NAT entry. Does not actually change the packet */ |
| /* in any way. */ |
| /* */ |
| /* This fucntion is in three main parts: (1) deal with creating a new NAT */ |
| /* structure for a "MAP" rule (outgoing NAT translation); (2) deal with */ |
| /* creating a new NAT structure for a "RDR" rule (incoming NAT translation) */ |
| /* and (3) building that structure and putting it into the NAT table(s). */ |
| /* */ |
| /* NOTE: natsave should NOT be used top point back to an ipstate_t struct */ |
| /* as it can result in memory being corrupted. */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_add(fin, np, natsave, flags, direction) |
| fr_info_t *fin; |
| ipnat_t *np; |
| nat_t **natsave; |
| u_int flags; |
| int direction; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| hostmap_t *hm = NULL; |
| nat_t *nat, *natl; |
| natstat_t *nsp; |
| u_int nflags; |
| natinfo_t ni; |
| int move; |
| #if SOLARIS && defined(_KERNEL) && (SOLARIS2 >= 6) && defined(ICK_M_CTL_MAGIC) |
| qpktinfo_t *qpi = fin->fin_qpi; |
| #endif |
| |
| nsp = &softn->ipf_nat_stats; |
| |
| if ((nsp->ns_active * 100 / softn->ipf_nat_table_max) > |
| softn->ipf_nat_table_wm_high) { |
| softn->ipf_nat_doflush = 1; |
| } |
| |
| if (nsp->ns_active >= softn->ipf_nat_table_max) { |
| NBUMPSIDE6(fin->fin_out, ns_table_max); |
| return NULL; |
| } |
| |
| move = 1; |
| nflags = np->in_flags & flags; |
| nflags &= NAT_FROMRULE; |
| |
| ni.nai_np = np; |
| ni.nai_dport = 0; |
| ni.nai_sport = 0; |
| |
| /* Give me a new nat */ |
| KMALLOC(nat, nat_t *); |
| if (nat == NULL) { |
| NBUMPSIDE6(fin->fin_out, ns_memfail); |
| /* |
| * Try to automatically tune the max # of entries in the |
| * table allowed to be less than what will cause kmem_alloc() |
| * to fail and try to eliminate panics due to out of memory |
| * conditions arising. |
| */ |
| if ((softn->ipf_nat_table_max > softn->ipf_nat_table_sz) && |
| (nsp->ns_active > 100)) { |
| softn->ipf_nat_table_max = nsp->ns_active - 100; |
| printf("table_max reduced to %d\n", |
| softn->ipf_nat_table_max); |
| } |
| return NULL; |
| } |
| |
| if (flags & IPN_ICMPQUERY) { |
| /* |
| * In the ICMP query NAT code, we translate the ICMP id fields |
| * to make them unique. This is indepedent of the ICMP type |
| * (e.g. in the unlikely event that a host sends an echo and |
| * an tstamp request with the same id, both packets will have |
| * their ip address/id field changed in the same way). |
| */ |
| /* The icmp6_id field is used by the sender to identify the |
| * process making the icmp request. (the receiver justs |
| * copies it back in its response). So, it closely matches |
| * the concept of source port. We overlay sport, so we can |
| * maximally reuse the existing code. |
| */ |
| ni.nai_sport = fin->fin_data[1]; |
| ni.nai_dport = 0; |
| } |
| |
| bzero((char *)nat, sizeof(*nat)); |
| nat->nat_flags = flags; |
| nat->nat_redir = np->in_redir; |
| nat->nat_dir = direction; |
| nat->nat_pr[0] = fin->fin_p; |
| nat->nat_pr[1] = fin->fin_p; |
| |
| /* |
| * Search the current table for a match and create a new mapping |
| * if there is none found. |
| */ |
| if (np->in_redir & NAT_DIVERTUDP) { |
| move = ipf_nat6_newdivert(fin, nat, &ni); |
| |
| } else if (np->in_redir & NAT_REWRITE) { |
| move = ipf_nat6_newrewrite(fin, nat, &ni); |
| |
| } else if (direction == NAT_OUTBOUND) { |
| /* |
| * We can now arrange to call this for the same connection |
| * because ipf_nat6_new doesn't protect the code path into |
| * this function. |
| */ |
| natl = ipf_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 = ipf_nat6_newmap(fin, nat, &ni); |
| } else { |
| /* |
| * NAT_INBOUND is used for redirects rules |
| */ |
| natl = ipf_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 = ipf_nat6_newrdr(fin, nat, &ni); |
| } |
| if (move == -1) |
| goto badnat; |
| |
| np = ni.nai_np; |
| |
| nat->nat_mssclamp = np->in_mssclamp; |
| nat->nat_me = natsave; |
| nat->nat_fr = fin->fin_fr; |
| nat->nat_rev = fin->fin_rev; |
| nat->nat_ptr = np; |
| nat->nat_dlocal = np->in_dlocal; |
| |
| if ((np->in_apr != NULL) && ((nat->nat_flags & NAT_SLAVE) == 0)) { |
| if (ipf_proxy_new(fin, nat) == -1) { |
| NBUMPSIDE6D(fin->fin_out, ns_appr_fail); |
| goto badnat; |
| } |
| } |
| |
| nat->nat_ifps[0] = np->in_ifps[0]; |
| if (np->in_ifps[0] != NULL) { |
| COPYIFNAME(np->in_v[0], np->in_ifps[0], nat->nat_ifnames[0]); |
| } |
| |
| nat->nat_ifps[1] = np->in_ifps[1]; |
| if (np->in_ifps[1] != NULL) { |
| COPYIFNAME(np->in_v[1], np->in_ifps[1], nat->nat_ifnames[1]); |
| } |
| |
| if (ipf_nat6_finalise(fin, nat) == -1) { |
| goto badnat; |
| } |
| |
| np->in_use++; |
| |
| if ((move == 1) && (np->in_flags & IPN_ROUNDR)) { |
| if ((np->in_redir & (NAT_REDIRECT|NAT_MAP)) == NAT_REDIRECT) { |
| ipf_nat6_delrdr(softn, np); |
| ipf_nat6_addrdr(softn, np); |
| } else if ((np->in_redir & (NAT_REDIRECT|NAT_MAP)) == NAT_MAP) { |
| ipf_nat6_delmap(softn, np); |
| ipf_nat6_addmap(softn, np); |
| } |
| } |
| |
| if (flags & SI_WILDP) |
| nsp->ns_wilds++; |
| softn->ipf_nat_stats.ns_proto[nat->nat_pr[0]]++; |
| |
| goto done; |
| badnat: |
| NBUMPSIDE6(fin->fin_out, ns_badnatnew); |
| if ((hm = nat->nat_hm) != NULL) |
| ipf_nat_hostmapdel(softc, &hm); |
| KFREE(nat); |
| nat = NULL; |
| done: |
| if (nat != NULL && np != NULL) |
| np->in_hits++; |
| if (natsave != NULL) |
| *natsave = nat; |
| return nat; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_finalise */ |
| /* Returns: int - 0 == sucess, -1 == failure */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT entry */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* This is the tail end of constructing a new NAT entry and is the same */ |
| /* for both IPv4 and IPv6. */ |
| /* ------------------------------------------------------------------------ */ |
| /*ARGSUSED*/ |
| int |
| ipf_nat6_finalise(fin, nat) |
| fr_info_t *fin; |
| nat_t *nat; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| u_32_t sum1, sum2, sumd; |
| frentry_t *fr; |
| |
| switch (fin->fin_p) |
| { |
| case IPPROTO_ICMPV6 : |
| sum1 = LONG_SUM6(&nat->nat_osrc6); |
| sum1 += ntohs(nat->nat_oicmpid); |
| sum2 = LONG_SUM6(&nat->nat_nsrc6); |
| sum2 += ntohs(nat->nat_nicmpid); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] = (sumd & 0xffff) + (sumd >> 16); |
| |
| sum1 = LONG_SUM6(&nat->nat_odst6); |
| sum2 = LONG_SUM6(&nat->nat_ndst6); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] += (sumd & 0xffff) + (sumd >> 16); |
| break; |
| |
| case IPPROTO_TCP : |
| case IPPROTO_UDP : |
| sum1 = LONG_SUM6(&nat->nat_osrc6); |
| sum1 += ntohs(nat->nat_osport); |
| sum2 = LONG_SUM6(&nat->nat_nsrc6); |
| sum2 += ntohs(nat->nat_nsport); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] = (sumd & 0xffff) + (sumd >> 16); |
| |
| sum1 = LONG_SUM6(&nat->nat_odst6); |
| sum1 += ntohs(nat->nat_odport); |
| sum2 = LONG_SUM6(&nat->nat_ndst6); |
| sum2 += ntohs(nat->nat_ndport); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] += (sumd & 0xffff) + (sumd >> 16); |
| break; |
| |
| default : |
| sum1 = LONG_SUM6(&nat->nat_osrc6); |
| sum2 = LONG_SUM6(&nat->nat_nsrc6); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] = (sumd & 0xffff) + (sumd >> 16); |
| |
| sum1 = LONG_SUM6(&nat->nat_odst6); |
| sum2 = LONG_SUM6(&nat->nat_ndst6); |
| CALC_SUMD(sum1, sum2, sumd); |
| nat->nat_sumd[0] += (sumd & 0xffff) + (sumd >> 16); |
| break; |
| } |
| |
| /* |
| * Compute the partial checksum, just in case. |
| * This is only ever placed into outbound packets so care needs |
| * to be taken over which pair of addresses are used. |
| */ |
| if (nat->nat_dir == NAT_OUTBOUND) { |
| sum1 = LONG_SUM6(&nat->nat_nsrc6); |
| sum1 += LONG_SUM6(&nat->nat_ndst6); |
| } else { |
| sum1 = LONG_SUM6(&nat->nat_osrc6); |
| sum1 += LONG_SUM6(&nat->nat_odst6); |
| } |
| sum1 += nat->nat_pr[1]; |
| nat->nat_sumd[1] = (sum1 & 0xffff) + (sum1 >> 16); |
| |
| if ((nat->nat_flags & SI_CLONE) == 0) |
| nat->nat_sync = ipf_sync_new(softc, SMC_NAT, fin, nat); |
| |
| if ((nat->nat_ifps[0] != NULL) && (nat->nat_ifps[0] != (void *)-1)) { |
| nat->nat_mtu[0] = GETIFMTU_6(nat->nat_ifps[0]); |
| } |
| |
| if ((nat->nat_ifps[1] != NULL) && (nat->nat_ifps[1] != (void *)-1)) { |
| nat->nat_mtu[1] = GETIFMTU_6(nat->nat_ifps[1]); |
| } |
| |
| nat->nat_v[0] = 6; |
| nat->nat_v[1] = 6; |
| |
| if (ipf_nat6_insert(softc, softn, nat) == 0) { |
| if (softn->ipf_nat_logging) |
| ipf_nat_log(softc, softn, nat, NL_NEW); |
| fr = nat->nat_fr; |
| if (fr != NULL) { |
| MUTEX_ENTER(&fr->fr_lock); |
| fr->fr_ref++; |
| MUTEX_EXIT(&fr->fr_lock); |
| } |
| return 0; |
| } |
| |
| NBUMPSIDE6D(fin->fin_out, ns_unfinalised); |
| /* |
| * nat6_insert failed, so cleanup time... |
| */ |
| if (nat->nat_sync != NULL) |
| ipf_sync_del_nat(softc->ipf_sync_soft, nat->nat_sync); |
| return -1; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_insert */ |
| /* Returns: int - 0 == sucess, -1 == failure */ |
| /* Parameters: softc(I) - pointer to soft context main structure */ |
| /* softn(I) - pointer to NAT context structure */ |
| /* nat(I) - pointer to NAT structure */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* Insert a NAT entry into the hash tables for searching and add it to the */ |
| /* list of active NAT entries. Adjust global counters when complete. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_insert(softc, softn, nat) |
| ipf_main_softc_t *softc; |
| ipf_nat_softc_t *softn; |
| nat_t *nat; |
| { |
| u_int hv0, hv1; |
| u_32_t sp, dp; |
| ipnat_t *in; |
| |
| /* |
| * Try and return an error as early as possible, so calculate the hash |
| * entry numbers first and then proceed. |
| */ |
| if ((nat->nat_flags & (SI_W_SPORT|SI_W_DPORT)) == 0) { |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| sp = nat->nat_osport; |
| dp = nat->nat_odport; |
| } else if ((nat->nat_flags & IPN_ICMPQUERY) != 0) { |
| sp = 0; |
| dp = nat->nat_oicmpid; |
| } else { |
| sp = 0; |
| dp = 0; |
| } |
| hv0 = NAT_HASH_FN6(&nat->nat_osrc6, sp, 0xffffffff); |
| hv0 = NAT_HASH_FN6(&nat->nat_odst6, hv0 + dp, |
| softn->ipf_nat_table_sz); |
| |
| /* |
| * TRACE nat6_osrc6, nat6_osport, nat6_odst6, |
| * nat6_odport, hv0 |
| */ |
| |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| sp = nat->nat_nsport; |
| dp = nat->nat_ndport; |
| } else if ((nat->nat_flags & IPN_ICMPQUERY) != 0) { |
| sp = 0; |
| dp = nat->nat_nicmpid; |
| } else { |
| sp = 0; |
| dp = 0; |
| } |
| hv1 = NAT_HASH_FN6(&nat->nat_nsrc6, sp, 0xffffffff); |
| hv1 = NAT_HASH_FN6(&nat->nat_ndst6, hv1 + dp, |
| softn->ipf_nat_table_sz); |
| /* |
| * TRACE nat6_nsrcaddr, nat6_nsport, nat6_ndstaddr, |
| * nat6_ndport, hv0 |
| */ |
| } else { |
| hv0 = NAT_HASH_FN6(&nat->nat_osrc6, 0, 0xffffffff); |
| hv0 = NAT_HASH_FN6(&nat->nat_odst6, hv0, |
| softn->ipf_nat_table_sz); |
| /* TRACE nat6_osrcip6, nat6_odstip6, hv0 */ |
| |
| hv1 = NAT_HASH_FN6(&nat->nat_nsrc6, 0, 0xffffffff); |
| hv1 = NAT_HASH_FN6(&nat->nat_ndst6, hv1, |
| softn->ipf_nat_table_sz); |
| /* TRACE nat6_nsrcip6, nat6_ndstip6, hv1 */ |
| } |
| |
| if ((nat->nat_dir & NAT_OUTBOUND) == NAT_OUTBOUND) { |
| nat->nat_hv[0] = hv0; |
| nat->nat_hv[1] = hv1; |
| } else { |
| nat->nat_hv[0] = hv1; |
| nat->nat_hv[1] = hv0; |
| } |
| |
| MUTEX_INIT(&nat->nat_lock, "nat entry lock"); |
| |
| in = nat->nat_ptr; |
| nat->nat_ref = nat->nat_me ? 2 : 1; |
| |
| nat->nat_ifnames[0][LIFNAMSIZ - 1] = '\0'; |
| nat->nat_ifps[0] = ipf_resolvenic(softc, nat->nat_ifnames[0], |
| nat->nat_v[0]); |
| |
| if (nat->nat_ifnames[1][0] != '\0') { |
| nat->nat_ifnames[1][LIFNAMSIZ - 1] = '\0'; |
| nat->nat_ifps[1] = ipf_resolvenic(softc, nat->nat_ifnames[1], |
| nat->nat_v[1]); |
| } else if (in->in_ifnames[1] != -1) { |
| char *name; |
| |
| name = in->in_names + in->in_ifnames[1]; |
| if (name[1] != '\0' && name[0] != '-' && name[0] != '*') { |
| (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]; |
| } |
| } |
| if ((nat->nat_ifps[0] != NULL) && (nat->nat_ifps[0] != (void *)-1)) { |
| nat->nat_mtu[0] = GETIFMTU_6(nat->nat_ifps[0]); |
| } |
| if ((nat->nat_ifps[1] != NULL) && (nat->nat_ifps[1] != (void *)-1)) { |
| nat->nat_mtu[1] = GETIFMTU_6(nat->nat_ifps[1]); |
| } |
| |
| return ipf_nat_hashtab_add(softc, softn, nat); |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_icmperrorlookup */ |
| /* Returns: nat6_t* - point to matching NAT structure */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* dir(I) - direction of packet (in/out) */ |
| /* */ |
| /* Check if the ICMP error message is related to an existing TCP, UDP or */ |
| /* ICMP query nat entry. It is assumed that the packet is already of the */ |
| /* the required length. */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_icmperrorlookup(fin, dir) |
| fr_info_t *fin; |
| int dir; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| struct icmp6_hdr *orgicmp; |
| nat_stat_side_t *nside; |
| int flags = 0, minlen; |
| tcphdr_t *tcp = NULL; |
| u_short data[2]; |
| ip6_t *oip6; |
| nat_t *nat; |
| u_int p; |
| |
| minlen = 40; |
| nside = &softn->ipf_nat_stats.ns_side6[fin->fin_out]; |
| /* |
| * Does it at least have the return (basic) IP header ? |
| * Only a basic IP header (no options) should be with an ICMP error |
| * header. Also, if it's not an error type, then return. |
| */ |
| if (!(fin->fin_flx & FI_ICMPERR)) { |
| ATOMIC_INCL(nside->ns_icmp_basic); |
| return NULL; |
| } |
| |
| /* |
| * Check packet size |
| */ |
| if (fin->fin_plen < ICMP6ERR_IPICMPHLEN) { |
| ATOMIC_INCL(nside->ns_icmp_size); |
| return NULL; |
| } |
| oip6 = (ip6_t *)((char *)fin->fin_dp + 8); |
| |
| /* |
| * Is the buffer big enough for all of it ? It's the size of the IP |
| * header claimed in the encapsulated part which is of concern. It |
| * may be too big to be in this buffer but not so big that it's |
| * outside the ICMP packet, leading to TCP deref's causing problems. |
| * This is possible because we don't know how big oip_hl is when we |
| * do the pullup early in ipf_check() and thus can't gaurantee it is |
| * all here now. |
| */ |
| #ifdef ipf_nat6_KERNEL |
| { |
| mb_t *m; |
| |
| m = fin->fin_m; |
| # if defined(MENTAT) |
| if ((char *)oip6 + fin->fin_dlen - ICMPERR_ICMPHLEN > |
| (char *)m->b_wptr) { |
| ATOMIC_INCL(nside->ns_icmp_mbuf); |
| return NULL; |
| } |
| # else |
| if ((char *)oip6 + fin->fin_dlen - ICMPERR_ICMPHLEN > |
| (char *)fin->fin_ip + M_LEN(m)) { |
| ATOMIC_INCL(nside->ns_icmp_mbuf); |
| return NULL; |
| } |
| # endif |
| } |
| #endif |
| |
| if (IP6_NEQ(&fin->fin_dst6, &oip6->ip6_src)) { |
| ATOMIC_INCL(nside->ns_icmp_address); |
| 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); |
| |
| /* see if this is related to an ICMP query */ |
| if (ipf_nat6_icmpquerytype(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; |
| /* |
| * NOTE : dir refers to the direction of the original |
| * ip packet. By definition the icmp error |
| * message flows in the opposite direction. |
| */ |
| if (dir == NAT_INBOUND) |
| nat = ipf_nat6_inlookup(fin, flags, p, |
| &oip6->ip6_dst, |
| &oip6->ip6_src); |
| else |
| nat = ipf_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; /* + 64bits of data to get ports */ |
| /* TRACE (fin,minlen) */ |
| if (fin->fin_plen < ICMPERR_IPICMPHLEN + minlen) { |
| ATOMIC_INCL(nside->ns_icmp_short); |
| 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 = ipf_nat6_inlookup(fin, flags, p, &oip6->ip6_dst, |
| &oip6->ip6_src); |
| } else { |
| nat = ipf_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) |
| nat = ipf_nat6_inlookup(fin, 0, p, &oip6->ip6_dst, |
| &oip6->ip6_src); |
| else |
| nat = ipf_nat6_outlookup(fin, 0, p, &oip6->ip6_dst, |
| &oip6->ip6_src); |
| |
| return nat; |
| } |
| |
| |
| /* result = ip1 - ip2 */ |
| u_32_t |
| ipf_nat6_ip6subtract(ip1, ip2) |
| i6addr_t *ip1, *ip2; |
| { |
| i6addr_t l1, l2, d; |
| u_short *s1, *s2, *ds; |
| u_32_t r; |
| int i; |
| |
| l1 = *ip1; |
| l2 = *ip2; |
| s1 = (u_short *)&l1; |
| s2 = (u_short *)&l2; |
| ds = (u_short *)&d; |
| |
| for (i = 7; i > 0; i--) { |
| if (s1[i] > s2[i]) { |
| ds[i] = s2[i] + 0x10000 - s1[i]; |
| s2[i - 1] += 0x10000; |
| } else { |
| ds[i] = s2[i] - s1[i]; |
| } |
| } |
| if (s2[0] > s1[0]) { |
| ds[0] = s2[0] + 0x10000 - s1[0]; |
| } else { |
| ds[0] = s2[0] - s1[0]; |
| } |
| |
| for (i = 0, r = 0; i < 8; i++) { |
| r += ds[i]; |
| } |
| |
| return r; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_icmperror */ |
| /* Returns: nat6_t* - point to matching NAT structure */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nflags(I) - NAT flags for this packet */ |
| /* dir(I) - direction of packet (in/out) */ |
| /* */ |
| /* Fix up an ICMP packet which is an error message for an existing NAT */ |
| /* session. This will correct both packet header data and checksums. */ |
| /* */ |
| /* This should *ONLY* be used for incoming ICMP error packets to make sure */ |
| /* a NAT'd ICMP packet gets correctly recognised. */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_icmperror(fin, nflags, dir) |
| fr_info_t *fin; |
| u_int *nflags; |
| int dir; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| u_32_t sum1, sum2, sumd, sumd2; |
| i6addr_t a1, a2, a3, a4; |
| struct icmp6_hdr *icmp6; |
| int flags, dlen, odst; |
| u_short *csump; |
| tcphdr_t *tcp; |
| ip6_t *oip6; |
| nat_t *nat; |
| void *dp; |
| |
| if ((fin->fin_flx & (FI_SHORT|FI_FRAGBODY))) { |
| NBUMPSIDE6D(fin->fin_out, ns_icmp_short); |
| return NULL; |
| } |
| |
| /* |
| * ipf_nat6_icmperrorlookup() will return NULL for `defective' packets. |
| */ |
| if ((fin->fin_v != 6) || !(nat = ipf_nat6_icmperrorlookup(fin, dir))) { |
| NBUMPSIDE6D(fin->fin_out, ns_icmp_notfound); |
| return NULL; |
| } |
| |
| tcp = NULL; |
| csump = NULL; |
| flags = 0; |
| sumd2 = 0; |
| *nflags = IPN_ICMPERR; |
| icmp6 = fin->fin_dp; |
| oip6 = (ip6_t *)((u_char *)icmp6 + sizeof(*icmp6)); |
| dp = (u_char *)oip6 + sizeof(*oip6); |
| if (oip6->ip6_nxt == IPPROTO_TCP) { |
| tcp = (tcphdr_t *)dp; |
| csump = (u_short *)&tcp->th_sum; |
| flags = IPN_TCP; |
| } else if (oip6->ip6_nxt == IPPROTO_UDP) { |
| udphdr_t *udp; |
| |
| udp = (udphdr_t *)dp; |
| tcp = (tcphdr_t *)dp; |
| csump = (u_short *)&udp->uh_sum; |
| flags = IPN_UDP; |
| } else if (oip6->ip6_nxt == IPPROTO_ICMPV6) |
| flags = IPN_ICMPQUERY; |
| dlen = fin->fin_plen - ((char *)dp - (char *)fin->fin_ip); |
| |
| /* |
| * Need to adjust ICMP header to include the real IP#'s and |
| * port #'s. Only apply a checksum change relative to the |
| * IP address change as it will be modified again in ipf_nat6_checkout |
| * for both address and port. Two checksum changes are |
| * necessary for the two header address changes. Be careful |
| * to only modify the checksum once for the port # and twice |
| * for the IP#. |
| */ |
| |
| /* |
| * Step 1 |
| * Fix the IP addresses in the offending IP packet. You also need |
| * to adjust the IP header checksum of that offending IP packet. |
| * |
| * Normally, you would expect that the ICMP checksum of the |
| * ICMP error message needs to be adjusted as well for the |
| * IP address change in oip. |
| * However, this is a NOP, because the ICMP checksum is |
| * calculated over the complete ICMP packet, which includes the |
| * changed oip IP addresses and oip6->ip6_sum. However, these |
| * two changes cancel each other out (if the delta for |
| * the IP address is x, then the delta for ip_sum is minus x), |
| * so no change in the icmp_cksum is necessary. |
| * |
| * Inbound ICMP |
| * ------------ |
| * MAP rule, SRC=a,DST=b -> SRC=c,DST=b |
| * - response to outgoing packet (a,b)=>(c,b) (OIP_SRC=c,OIP_DST=b) |
| * - OIP_SRC(c)=nat6_newsrcip, OIP_DST(b)=nat6_newdstip |
| *=> OIP_SRC(c)=nat6_oldsrcip, OIP_DST(b)=nat6_olddstip |
| * |
| * RDR rule, SRC=a,DST=b -> SRC=a,DST=c |
| * - response to outgoing packet (c,a)=>(b,a) (OIP_SRC=b,OIP_DST=a) |
| * - OIP_SRC(b)=nat6_olddstip, OIP_DST(a)=nat6_oldsrcip |
| *=> OIP_SRC(b)=nat6_newdstip, OIP_DST(a)=nat6_newsrcip |
| * |
| * REWRITE out rule, SRC=a,DST=b -> SRC=c,DST=d |
| * - response to outgoing packet (a,b)=>(c,d) (OIP_SRC=c,OIP_DST=d) |
| * - OIP_SRC(c)=nat6_newsrcip, OIP_DST(d)=nat6_newdstip |
| *=> OIP_SRC(c)=nat6_oldsrcip, OIP_DST(d)=nat6_olddstip |
| * |
| * REWRITE in rule, SRC=a,DST=b -> SRC=c,DST=d |
| * - response to outgoing packet (d,c)=>(b,a) (OIP_SRC=b,OIP_DST=a) |
| * - OIP_SRC(b)=nat6_olddstip, OIP_DST(a)=nat6_oldsrcip |
| *=> OIP_SRC(b)=nat6_newdstip, OIP_DST(a)=nat6_newsrcip |
| * |
| * Outbound ICMP |
| * ------------- |
| * MAP rule, SRC=a,DST=b -> SRC=c,DST=b |
| * - response to incoming packet (b,c)=>(b,a) (OIP_SRC=b,OIP_DST=a) |
| * - OIP_SRC(b)=nat6_olddstip, OIP_DST(a)=nat6_oldsrcip |
| *=> OIP_SRC(b)=nat6_newdstip, OIP_DST(a)=nat6_newsrcip |
| * |
| * RDR rule, SRC=a,DST=b -> SRC=a,DST=c |
| * - response to incoming packet (a,b)=>(a,c) (OIP_SRC=a,OIP_DST=c) |
| * - OIP_SRC(a)=nat6_newsrcip, OIP_DST(c)=nat6_newdstip |
| *=> OIP_SRC(a)=nat6_oldsrcip, OIP_DST(c)=nat6_olddstip |
| * |
| * REWRITE out rule, SRC=a,DST=b -> SRC=c,DST=d |
| * - response to incoming packet (d,c)=>(b,a) (OIP_SRC=c,OIP_DST=d) |
| * - OIP_SRC(c)=nat6_olddstip, OIP_DST(d)=nat6_oldsrcip |
| *=> OIP_SRC(b)=nat6_newdstip, OIP_DST(a)=nat6_newsrcip |
| * |
| * REWRITE in rule, SRC=a,DST=b -> SRC=c,DST=d |
| * - response to incoming packet (a,b)=>(c,d) (OIP_SRC=b,OIP_DST=a) |
| * - OIP_SRC(b)=nat6_newsrcip, OIP_DST(a)=nat6_newdstip |
| *=> OIP_SRC(a)=nat6_oldsrcip, OIP_DST(c)=nat6_olddstip |
| */ |
| |
| if (((fin->fin_out == 0) && ((nat->nat_redir & NAT_MAP) != 0)) || |
| ((fin->fin_out == 1) && ((nat->nat_redir & NAT_REDIRECT) != 0))) { |
| a1 = nat->nat_osrc6; |
| a4.in6 = oip6->ip6_src; |
| a3 = nat->nat_odst6; |
| a2.in6 = oip6->ip6_dst; |
| oip6->ip6_src = a1.in6; |
| oip6->ip6_dst = a3.in6; |
| odst = 1; |
| } else { |
| a1 = nat->nat_ndst6; |
| a2.in6 = oip6->ip6_dst; |
| a3 = nat->nat_nsrc6; |
| a4.in6 = oip6->ip6_src; |
| oip6->ip6_dst = a3.in6; |
| oip6->ip6_src = a1.in6; |
| odst = 0; |
| } |
| |
| sumd = 0; |
| if (IP6_NEQ(&a3, &a2) || IP6_NEQ(&a1, &a4)) { |
| if (IP6_GT(&a3, &a2)) { |
| sumd = ipf_nat6_ip6subtract(&a2, &a3); |
| sumd--; |
| } else { |
| sumd = ipf_nat6_ip6subtract(&a2, &a3); |
| } |
| if (IP6_GT(&a1, &a4)) { |
| sumd += ipf_nat6_ip6subtract(&a4, &a1); |
| sumd--; |
| } else { |
| sumd += ipf_nat6_ip6subtract(&a4, &a1); |
| } |
| sumd = ~sumd; |
| } |
| |
| sumd2 = sumd; |
| sum1 = 0; |
| sum2 = 0; |
| |
| /* |
| * Fix UDP pseudo header checksum to compensate for the |
| * IP address change. |
| */ |
| if (((flags & IPN_TCPUDP) != 0) && (dlen >= 4)) { |
| u_32_t sum3, sum4; |
| /* |
| * Step 2 : |
| * For offending TCP/UDP IP packets, translate the ports as |
| * well, based on the NAT specification. Of course such |
| * a change may be reflected in the ICMP checksum as well. |
| * |
| * Since the port fields are part of the TCP/UDP checksum |
| * of the offending IP packet, you need to adjust that checksum |
| * as well... except that the change in the port numbers should |
| * be offset by the checksum change. However, the TCP/UDP |
| * checksum will also need to change if there has been an |
| * IP address change. |
| */ |
| if (odst == 1) { |
| sum1 = ntohs(nat->nat_osport); |
| sum4 = ntohs(tcp->th_sport); |
| sum3 = ntohs(nat->nat_odport); |
| sum2 = ntohs(tcp->th_dport); |
| |
| tcp->th_sport = htons(sum1); |
| tcp->th_dport = htons(sum3); |
| } else { |
| sum1 = ntohs(nat->nat_ndport); |
| sum2 = ntohs(tcp->th_dport); |
| sum3 = ntohs(nat->nat_nsport); |
| sum4 = ntohs(tcp->th_sport); |
| |
| tcp->th_dport = htons(sum3); |
| tcp->th_sport = htons(sum1); |
| } |
| sumd += sum1 - sum4; |
| sumd += sum3 - sum2; |
| |
| if (sumd != 0 || sumd2 != 0) { |
| /* |
| * At this point, sumd is the delta to apply to the |
| * TCP/UDP header, given the changes in both the IP |
| * address and the ports and sumd2 is the delta to |
| * apply to the ICMP header, given the IP address |
| * change delta that may need to be applied to the |
| * TCP/UDP checksum instead. |
| * |
| * If we will both the IP and TCP/UDP checksums |
| * then the ICMP checksum changes by the address |
| * delta applied to the TCP/UDP checksum. If we |
| * do not change the TCP/UDP checksum them we |
| * apply the delta in ports to the ICMP checksum. |
| */ |
| if (oip6->ip6_nxt == IPPROTO_UDP) { |
| if ((dlen >= 8) && (*csump != 0)) { |
| ipf_fix_datacksum(csump, sumd); |
| } else { |
| sumd2 = sum4 - sum1; |
| if (sum1 > sum4) |
| sumd2--; |
| sumd2 += sum2 - sum3; |
| if (sum3 > sum2) |
| sumd2--; |
| } |
| } else if (oip6->ip6_nxt == IPPROTO_TCP) { |
| if (dlen >= 18) { |
| ipf_fix_datacksum(csump, sumd); |
| } else { |
| sumd2 = sum4 - sum1; |
| if (sum1 > sum4) |
| sumd2--; |
| sumd2 += sum2 - sum3; |
| if (sum3 > sum2) |
| sumd2--; |
| } |
| } |
| if (sumd2 != 0) { |
| sumd2 = (sumd2 & 0xffff) + (sumd2 >> 16); |
| sumd2 = (sumd2 & 0xffff) + (sumd2 >> 16); |
| sumd2 = (sumd2 & 0xffff) + (sumd2 >> 16); |
| ipf_fix_incksum(0, &icmp6->icmp6_cksum, |
| sumd2, 0); |
| } |
| } |
| } else if (((flags & IPN_ICMPQUERY) != 0) && (dlen >= 8)) { |
| struct icmp6_hdr *orgicmp; |
| |
| /* |
| * XXX - what if this is bogus hl and we go off the end ? |
| * In this case, ipf_nat6_icmperrorlookup() will have |
| * returned NULL. |
| */ |
| orgicmp = (struct icmp6_hdr *)dp; |
| |
| if (odst == 1) { |
| if (orgicmp->icmp6_id != nat->nat_osport) { |
| |
| /* |
| * Fix ICMP checksum (of the offening ICMP |
| * query packet) to compensate the change |
| * in the ICMP id of the offending ICMP |
| * packet. |
| * |
| * Since you modify orgicmp->icmp6_id with |
| * a delta (say x) and you compensate that |
| * in origicmp->icmp6_cksum with a delta |
| * minus x, you don't have to adjust the |
| * overall icmp->icmp6_cksum |
| */ |
| sum1 = ntohs(orgicmp->icmp6_id); |
| sum2 = ntohs(nat->nat_osport); |
| CALC_SUMD(sum1, sum2, sumd); |
| orgicmp->icmp6_id = nat->nat_oicmpid; |
| ipf_fix_datacksum(&orgicmp->icmp6_cksum, sumd); |
| } |
| } /* nat6_dir == NAT_INBOUND is impossible for icmp queries */ |
| } |
| return nat; |
| } |
| |
| |
| /* |
| * MAP-IN MAP-OUT RDR-IN RDR-OUT |
| * osrc X == src == src X |
| * odst X == dst == dst X |
| * nsrc == dst X X == dst |
| * ndst == src X X == src |
| * MAP = NAT_OUTBOUND, RDR = NAT_INBOUND |
| */ |
| /* |
| * NB: these lookups don't lock access to the list, it assumed that it has |
| * already been done! |
| */ |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_inlookup */ |
| /* Returns: nat6_t* - NULL == no match, */ |
| /* else pointer to matching NAT entry */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* flags(I) - NAT flags for this packet */ |
| /* p(I) - protocol for this packet */ |
| /* src(I) - source IP address */ |
| /* mapdst(I) - destination IP address */ |
| /* */ |
| /* Lookup a nat entry based on the mapped destination ip address/port and */ |
| /* real source address/port. We use this lookup when receiving a packet, */ |
| /* we're looking for a table entry, based on the destination address. */ |
| /* */ |
| /* NOTE: THE PACKET BEING CHECKED (IF FOUND) HAS A MAPPING ALREADY. */ |
| /* */ |
| /* NOTE: IT IS ASSUMED THAT IS ONLY HELD WITH A READ LOCK WHEN */ |
| /* THIS FUNCTION IS CALLED WITH NAT_SEARCH SET IN nflags. */ |
| /* */ |
| /* flags -> relevant are IPN_UDP/IPN_TCP/IPN_ICMPQUERY that indicate if */ |
| /* the packet is of said protocol */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_inlookup(fin, flags, p, src, mapdst) |
| fr_info_t *fin; |
| u_int flags, p; |
| struct in6_addr *src , *mapdst; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| u_short sport, dport; |
| ipnat_t *ipn; |
| nat_t *nat; |
| int nflags; |
| i6addr_t dst; |
| void *ifp; |
| u_int hv; |
| |
| ifp = fin->fin_ifp; |
| sport = 0; |
| dport = 0; |
| dst.in6 = *mapdst; |
| |
| 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, softn->ipf_nat_table_sz); |
| nat = softn->ipf_nat_table[1][hv]; |
| /* TRACE dst, dport, src, sport, hv, nat */ |
| |
| for (; nat; nat = nat->nat_hnext[1]) { |
| if (nat->nat_ifps[0] != NULL) { |
| if ((ifp != NULL) && (ifp != nat->nat_ifps[0])) |
| continue; |
| } |
| |
| if (nat->nat_pr[0] != p) |
| continue; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| if (nat->nat_v[0] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_osrc6, src) || |
| IP6_NEQ(&nat->nat_odst6, &dst)) |
| continue; |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| if (nat->nat_osport != sport) |
| continue; |
| if (nat->nat_odport != dport) |
| continue; |
| |
| } else if (p == IPPROTO_ICMPV6) { |
| if (nat->nat_oicmpid != dport) { |
| continue; |
| } |
| } |
| break; |
| case NAT_OUTBOUND : |
| if (nat->nat_v[1] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_ndst6, src) || |
| IP6_NEQ(&nat->nat_nsrc6, &dst)) |
| continue; |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| if (nat->nat_ndport != sport) |
| continue; |
| if (nat->nat_nsport != dport) |
| continue; |
| |
| } else if (p == IPPROTO_ICMPV6) { |
| if (nat->nat_nicmpid != dport) { |
| continue; |
| } |
| } |
| break; |
| } |
| |
| |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| ipn = nat->nat_ptr; |
| if ((ipn != NULL) && (nat->nat_aps != NULL)) |
| if (ipf_proxy_match(fin, nat) != 0) |
| continue; |
| } |
| if ((nat->nat_ifps[0] == NULL) && (ifp != NULL)) { |
| nat->nat_ifps[0] = ifp; |
| nat->nat_mtu[0] = GETIFMTU_6(ifp); |
| } |
| return nat; |
| } |
| |
| /* |
| * So if we didn't find it but there are wildcard members in the hash |
| * table, go back and look for them. We do this search and update here |
| * because it is modifying the NAT table and we want to do this only |
| * for the first packet that matches. The exception, of course, is |
| * for "dummy" (FI_IGNORE) lookups. |
| */ |
| find_in_wild_ports: |
| if (!(flags & NAT_TCPUDP) || !(flags & NAT_SEARCH)) { |
| NBUMPSIDE6DX(0, ns_lookup_miss, ns_lookup_miss_1); |
| return NULL; |
| } |
| if (softn->ipf_nat_stats.ns_wilds == 0 || (fin->fin_flx & FI_NOWILD)) { |
| NBUMPSIDE6D(0, ns_lookup_nowild); |
| return NULL; |
| } |
| |
| RWLOCK_EXIT(&softc->ipf_nat); |
| |
| hv = NAT_HASH_FN6(&dst, 0, 0xffffffff); |
| hv = NAT_HASH_FN6(src, hv, softn->ipf_nat_table_sz); |
| WRITE_ENTER(&softc->ipf_nat); |
| |
| nat = softn->ipf_nat_table[1][hv]; |
| /* TRACE dst, src, hv, nat */ |
| for (; nat; nat = nat->nat_hnext[1]) { |
| if (nat->nat_ifps[0] != NULL) { |
| if ((ifp != NULL) && (ifp != nat->nat_ifps[0])) |
| continue; |
| } |
| |
| if (nat->nat_pr[0] != fin->fin_p) |
| continue; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| if (nat->nat_v[0] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_osrc6, src) || |
| IP6_NEQ(&nat->nat_odst6, &dst)) |
| continue; |
| break; |
| case NAT_OUTBOUND : |
| if (nat->nat_v[1] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_ndst6, src) || |
| IP6_NEQ(&nat->nat_nsrc6, &dst)) |
| continue; |
| break; |
| } |
| |
| nflags = nat->nat_flags; |
| if (!(nflags & (NAT_TCPUDP|SI_WILDP))) |
| continue; |
| |
| if (ipf_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 = ipf_nat_clone(fin, nat); |
| if (nat == NULL) |
| break; |
| } else { |
| MUTEX_ENTER(&softn->ipf_nat_new); |
| softn->ipf_nat_stats.ns_wilds--; |
| MUTEX_EXIT(&softn->ipf_nat_new); |
| } |
| |
| if (nat->nat_dir == NAT_INBOUND) { |
| if (nat->nat_osport == 0) { |
| nat->nat_osport = sport; |
| nat->nat_nsport = sport; |
| } |
| if (nat->nat_odport == 0) { |
| nat->nat_odport = dport; |
| nat->nat_ndport = dport; |
| } |
| } else { |
| if (nat->nat_osport == 0) { |
| nat->nat_osport = dport; |
| nat->nat_nsport = dport; |
| } |
| if (nat->nat_odport == 0) { |
| nat->nat_odport = sport; |
| nat->nat_ndport = sport; |
| } |
| } |
| if ((nat->nat_ifps[0] == NULL) && (ifp != NULL)) { |
| nat->nat_ifps[0] = ifp; |
| nat->nat_mtu[0] = GETIFMTU_6(ifp); |
| } |
| nat->nat_flags &= ~(SI_W_DPORT|SI_W_SPORT); |
| ipf_nat6_tabmove(softn, nat); |
| break; |
| } |
| } |
| |
| MUTEX_DOWNGRADE(&softc->ipf_nat); |
| |
| if (nat == NULL) { |
| NBUMPSIDE6DX(0, ns_lookup_miss, ns_lookup_miss_2); |
| } |
| return nat; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_tabmove */ |
| /* Returns: Nil */ |
| /* Parameters: nat(I) - pointer to NAT structure */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* This function is only called for TCP/UDP NAT table entries where the */ |
| /* original was placed in the table without hashing on the ports and we now */ |
| /* want to include hashing on port numbers. */ |
| /* ------------------------------------------------------------------------ */ |
| static void |
| ipf_nat6_tabmove(softn, nat) |
| ipf_nat_softc_t *softn; |
| nat_t *nat; |
| { |
| u_int rhv0, rhv1, hv0, hv1; |
| nat_t **natp; |
| |
| if (nat->nat_flags & SI_CLONE) |
| return; |
| |
| /* |
| * Remove the NAT entry from the old location |
| */ |
| if (nat->nat_hnext[0]) |
| nat->nat_hnext[0]->nat_phnext[0] = nat->nat_phnext[0]; |
| *nat->nat_phnext[0] = nat->nat_hnext[0]; |
| softn->ipf_nat_stats.ns_side[0].ns_bucketlen[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]; |
| softn->ipf_nat_stats.ns_side[1].ns_bucketlen[nat->nat_hv[1]]--; |
| |
| /* |
| * Add into the NAT table in the new position |
| */ |
| rhv0 = NAT_HASH_FN6(&nat->nat_osrc6, nat->nat_osport, 0xffffffff); |
| rhv0 = NAT_HASH_FN6(&nat->nat_odst6, rhv0 + nat->nat_odport, |
| softn->ipf_nat_table_sz); |
| rhv1 = NAT_HASH_FN6(&nat->nat_nsrc6, nat->nat_nsport, 0xffffffff); |
| rhv1 = NAT_HASH_FN6(&nat->nat_ndst6, rhv1 + nat->nat_ndport, |
| softn->ipf_nat_table_sz); |
| |
| if ((nat->nat_dir & NAT_OUTBOUND) == NAT_OUTBOUND) { |
| nat->nat_hv[0] = rhv0; |
| nat->nat_hv[1] = rhv1; |
| } else { |
| nat->nat_hv[0] = rhv1; |
| nat->nat_hv[1] = rhv0; |
| } |
| |
| hv0 = nat->nat_hv[0] % softn->ipf_nat_table_sz; |
| hv1 = nat->nat_hv[1] % softn->ipf_nat_table_sz; |
| |
| /* TRACE nat_osrc6, nat_osport, nat_odst6, nat_odport, hv0 */ |
| /* TRACE nat_nsrc6, nat_nsport, nat_ndst6, nat_ndport, hv1 */ |
| |
| natp = &softn->ipf_nat_table[0][hv0]; |
| if (*natp) |
| (*natp)->nat_phnext[0] = &nat->nat_hnext[0]; |
| nat->nat_phnext[0] = natp; |
| nat->nat_hnext[0] = *natp; |
| *natp = nat; |
| softn->ipf_nat_stats.ns_side[0].ns_bucketlen[hv0]++; |
| |
| natp = &softn->ipf_nat_table[1][hv1]; |
| if (*natp) |
| (*natp)->nat_phnext[1] = &nat->nat_hnext[1]; |
| nat->nat_phnext[1] = natp; |
| nat->nat_hnext[1] = *natp; |
| *natp = nat; |
| softn->ipf_nat_stats.ns_side[1].ns_bucketlen[hv1]++; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_outlookup */ |
| /* Returns: nat6_t* - NULL == no match, */ |
| /* else pointer to matching NAT entry */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* flags(I) - NAT flags for this packet */ |
| /* p(I) - protocol for this packet */ |
| /* src(I) - source IP address */ |
| /* dst(I) - destination IP address */ |
| /* rw(I) - 1 == write lock on held, 0 == read lock. */ |
| /* */ |
| /* Lookup a nat entry based on the source 'real' ip address/port and */ |
| /* destination address/port. We use this lookup when sending a packet out, */ |
| /* we're looking for a table entry, based on the source address. */ |
| /* */ |
| /* NOTE: THE PACKET BEING CHECKED (IF FOUND) HAS A MAPPING ALREADY. */ |
| /* */ |
| /* NOTE: IT IS ASSUMED THAT IS ONLY HELD WITH A READ LOCK WHEN */ |
| /* THIS FUNCTION IS CALLED WITH NAT_SEARCH SET IN nflags. */ |
| /* */ |
| /* flags -> relevant are IPN_UDP/IPN_TCP/IPN_ICMPQUERY that indicate if */ |
| /* the packet is of said protocol */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_outlookup(fin, flags, p, src, dst) |
| fr_info_t *fin; |
| u_int flags, p; |
| struct in6_addr *src , *dst; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| u_short sport, dport; |
| ipnat_t *ipn; |
| nat_t *nat; |
| void *ifp; |
| u_int hv; |
| |
| ifp = fin->fin_ifp; |
| 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, softn->ipf_nat_table_sz); |
| nat = softn->ipf_nat_table[0][hv]; |
| |
| /* TRACE src, sport, dst, dport, hv, nat */ |
| |
| for (; nat; nat = nat->nat_hnext[0]) { |
| if (nat->nat_ifps[1] != NULL) { |
| if ((ifp != NULL) && (ifp != nat->nat_ifps[1])) |
| continue; |
| } |
| |
| if (nat->nat_pr[1] != p) |
| continue; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| if (nat->nat_v[1] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_ndst6, src) || |
| IP6_NEQ(&nat->nat_nsrc6, dst)) |
| continue; |
| |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| if (nat->nat_ndport != sport) |
| continue; |
| if (nat->nat_nsport != dport) |
| continue; |
| |
| } else if (p == IPPROTO_ICMPV6) { |
| if (nat->nat_nicmpid != dport) { |
| continue; |
| } |
| } |
| break; |
| case NAT_OUTBOUND : |
| if (nat->nat_v[0] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_osrc6, src) || |
| IP6_NEQ(&nat->nat_odst6, dst)) |
| continue; |
| |
| if ((nat->nat_flags & IPN_TCPUDP) != 0) { |
| if (nat->nat_odport != dport) |
| continue; |
| if (nat->nat_osport != sport) |
| continue; |
| |
| } else if (p == IPPROTO_ICMPV6) { |
| if (nat->nat_oicmpid != dport) { |
| continue; |
| } |
| } |
| break; |
| } |
| |
| ipn = nat->nat_ptr; |
| if ((ipn != NULL) && (nat->nat_aps != NULL)) |
| if (ipf_proxy_match(fin, nat) != 0) |
| continue; |
| |
| if ((nat->nat_ifps[1] == NULL) && (ifp != NULL)) { |
| nat->nat_ifps[1] = ifp; |
| nat->nat_mtu[1] = GETIFMTU_6(ifp); |
| } |
| return nat; |
| } |
| |
| /* |
| * So if we didn't find it but there are wildcard members in the hash |
| * table, go back and look for them. We do this search and update here |
| * because it is modifying the NAT table and we want to do this only |
| * for the first packet that matches. The exception, of course, is |
| * for "dummy" (FI_IGNORE) lookups. |
| */ |
| find_out_wild_ports: |
| if (!(flags & NAT_TCPUDP) || !(flags & NAT_SEARCH)) { |
| NBUMPSIDE6DX(1, ns_lookup_miss, ns_lookup_miss_3); |
| return NULL; |
| } |
| if (softn->ipf_nat_stats.ns_wilds == 0 || (fin->fin_flx & FI_NOWILD)) { |
| NBUMPSIDE6D(1, ns_lookup_nowild); |
| return NULL; |
| } |
| |
| RWLOCK_EXIT(&softc->ipf_nat); |
| |
| hv = NAT_HASH_FN6(src, 0, 0xffffffff); |
| hv = NAT_HASH_FN6(dst, hv, softn->ipf_nat_table_sz); |
| |
| WRITE_ENTER(&softc->ipf_nat); |
| |
| nat = softn->ipf_nat_table[0][hv]; |
| for (; nat; nat = nat->nat_hnext[0]) { |
| if (nat->nat_ifps[1] != NULL) { |
| if ((ifp != NULL) && (ifp != nat->nat_ifps[1])) |
| continue; |
| } |
| |
| if (nat->nat_pr[1] != fin->fin_p) |
| continue; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| if (nat->nat_v[1] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_ndst6, src) || |
| IP6_NEQ(&nat->nat_nsrc6, dst)) |
| continue; |
| break; |
| case NAT_OUTBOUND : |
| if (nat->nat_v[0] != 6) |
| continue; |
| if (IP6_NEQ(&nat->nat_osrc6, src) || |
| IP6_NEQ(&nat->nat_odst6, dst)) |
| continue; |
| break; |
| } |
| |
| if (!(nat->nat_flags & (NAT_TCPUDP|SI_WILDP))) |
| continue; |
| |
| if (ipf_nat_wildok(nat, (int)sport, (int)dport, nat->nat_flags, |
| NAT_OUTBOUND) == 1) { |
| if ((fin->fin_flx & FI_IGNORE) != 0) |
| break; |
| if ((nat->nat_flags & SI_CLONE) != 0) { |
| nat = ipf_nat_clone(fin, nat); |
| if (nat == NULL) |
| break; |
| } else { |
| MUTEX_ENTER(&softn->ipf_nat_new); |
| softn->ipf_nat_stats.ns_wilds--; |
| MUTEX_EXIT(&softn->ipf_nat_new); |
| } |
| |
| if (nat->nat_dir == NAT_OUTBOUND) { |
| if (nat->nat_osport == 0) { |
| nat->nat_osport = sport; |
| nat->nat_nsport = sport; |
| } |
| if (nat->nat_odport == 0) { |
| nat->nat_odport = dport; |
| nat->nat_ndport = dport; |
| } |
| } else { |
| if (nat->nat_osport == 0) { |
| nat->nat_osport = dport; |
| nat->nat_nsport = dport; |
| } |
| if (nat->nat_odport == 0) { |
| nat->nat_odport = sport; |
| nat->nat_ndport = sport; |
| } |
| } |
| if ((nat->nat_ifps[1] == NULL) && (ifp != NULL)) { |
| nat->nat_ifps[1] = ifp; |
| nat->nat_mtu[1] = GETIFMTU_6(ifp); |
| } |
| nat->nat_flags &= ~(SI_W_DPORT|SI_W_SPORT); |
| ipf_nat6_tabmove(softn, nat); |
| break; |
| } |
| } |
| |
| MUTEX_DOWNGRADE(&softc->ipf_nat); |
| |
| if (nat == NULL) { |
| NBUMPSIDE6DX(1, ns_lookup_miss, ns_lookup_miss_4); |
| } |
| return nat; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_lookupredir */ |
| /* Returns: nat6_t* - NULL == no match, */ |
| /* else pointer to matching NAT entry */ |
| /* Parameters: np(I) - pointer to description of packet to find NAT table */ |
| /* entry for. */ |
| /* */ |
| /* Lookup the NAT tables to search for a matching redirect */ |
| /* The contents of natlookup_t should imitate those found in a packet that */ |
| /* would be translated - ie a packet coming in for RDR or going out for MAP.*/ |
| /* We can do the lookup in one of two ways, imitating an inbound or */ |
| /* outbound packet. By default we assume outbound, unless IPN_IN is set. */ |
| /* For IN, the fields are set as follows: */ |
| /* nl_real* = source information */ |
| /* nl_out* = destination information (translated) */ |
| /* For an out packet, the fields are set like this: */ |
| /* nl_in* = source information (untranslated) */ |
| /* nl_out* = destination information (translated) */ |
| /* ------------------------------------------------------------------------ */ |
| nat_t * |
| ipf_nat6_lookupredir(np) |
| natlookup_t *np; |
| { |
| 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; |
| |
| /* |
| * We can do two sorts of lookups: |
| * - IPN_IN: we have the `real' and `out' address, look for `in'. |
| * - default: we have the `in' and `out' address, look for `real'. |
| */ |
| if (np->nl_flags & IPN_IN) { |
| if ((nat = ipf_nat6_inlookup(&fi, np->nl_flags, fi.fin_p, |
| &np->nl_realip6, |
| &np->nl_outip6))) { |
| np->nl_inip6 = nat->nat_odst6.in6; |
| np->nl_inport = nat->nat_odport; |
| } |
| } else { |
| /* |
| * If nl_inip is non null, this is a lookup based on the real |
| * ip address. Else, we use the fake. |
| */ |
| if ((nat = ipf_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_pr[0]; |
| fin.fin_data[0] = ntohs(nat->nat_ndport); |
| fin.fin_data[1] = ntohs(nat->nat_nsport); |
| if (ipf_nat6_inlookup(&fin, np->nl_flags, |
| fin.fin_p, |
| &nat->nat_ndst6.in6, |
| &nat->nat_nsrc6.in6) != |
| NULL) { |
| np->nl_flags &= ~IPN_FINDFORWARD; |
| } |
| } |
| |
| np->nl_realip6 = nat->nat_ndst6.in6; |
| np->nl_realport = nat->nat_ndport; |
| } |
| } |
| |
| return nat; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_match */ |
| /* Returns: int - 0 == no match, 1 == match */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* np(I) - pointer to NAT rule */ |
| /* */ |
| /* Pull the matching of a packet against a NAT rule out of that complex */ |
| /* loop inside ipf_nat6_checkin() and lay it out properly in its own */ |
| /* function. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_match(fin, np) |
| fr_info_t *fin; |
| ipnat_t *np; |
| { |
| frtuc_t *ft; |
| int match; |
| |
| match = 0; |
| switch (np->in_osrcatype) |
| { |
| case FRI_NORMAL : |
| match = IP6_MASKNEQ(&fin->fin_src6, &np->in_osrcmsk6, |
| &np->in_osrcip6); |
| break; |
| case FRI_LOOKUP : |
| match = (*np->in_osrcfunc)(fin->fin_main_soft, np->in_osrcptr, |
| 6, &fin->fin_src6, fin->fin_plen); |
| break; |
| } |
| match ^= ((np->in_flags & IPN_NOTSRC) != 0); |
| if (match) |
| return 0; |
| |
| match = 0; |
| switch (np->in_odstatype) |
| { |
| case FRI_NORMAL : |
| match = IP6_MASKNEQ(&fin->fin_dst6, &np->in_odstmsk6, |
| &np->in_odstip6); |
| break; |
| case FRI_LOOKUP : |
| match = (*np->in_odstfunc)(fin->fin_main_soft, np->in_odstptr, |
| 6, &fin->fin_dst6, fin->fin_plen); |
| break; |
| } |
| |
| match ^= ((np->in_flags & IPN_NOTDST) != 0); |
| if (match) |
| 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 ipf_tcpudpchk(&fin->fin_fi, ft); |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_checkout */ |
| /* Returns: int - -1 == packet failed NAT checks so block it, */ |
| /* 0 == no packet translation occurred, */ |
| /* 1 == packet was successfully translated. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* passp(I) - pointer to filtering result flags */ |
| /* */ |
| /* Check to see if an outcoming packet should be changed. ICMP packets are */ |
| /* first checked to see if they match an existing entry (if an error), */ |
| /* otherwise a search of the current NAT table is made. If neither results */ |
| /* in a match then a search for a matching NAT rule is made. Create a new */ |
| /* NAT entry if a we matched a NAT rule. Lastly, actually change the */ |
| /* packet header(s) as required. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_checkout(fin, passp) |
| fr_info_t *fin; |
| u_32_t *passp; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| struct icmp6_hdr *icmp6 = NULL; |
| struct ifnet *ifp, *sifp; |
| tcphdr_t *tcp = NULL; |
| int rval, natfailed; |
| ipnat_t *np = NULL; |
| u_int nflags = 0; |
| i6addr_t ipa, iph; |
| int natadd = 1; |
| frentry_t *fr; |
| nat_t *nat; |
| |
| if (softn->ipf_nat_stats.ns_rules == 0 || softn->ipf_nat_lock != 0) |
| return 0; |
| |
| icmp6 = NULL; |
| natfailed = 0; |
| fr = fin->fin_fr; |
| sifp = fin->fin_ifp; |
| if (fr != NULL) { |
| ifp = fr->fr_tifs[fin->fin_rev].fd_ptr; |
| if ((ifp != NULL) && (ifp != (void *)-1)) |
| fin->fin_ifp = 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 : |
| icmp6 = fin->fin_dp; |
| |
| /* |
| * Apart from ECHO request and reply, all other |
| * informational messages should not be translated |
| * so as to keep IPv6 working. |
| */ |
| if (icmp6->icmp6_type > ICMP6_ECHO_REPLY) |
| return 0; |
| |
| /* |
| * This is an incoming packet, so the destination is |
| * the icmp6_id and the source port equals 0 |
| */ |
| if ((fin->fin_flx & FI_ICMPQUERY) != 0) |
| nflags = IPN_ICMPQUERY; |
| break; |
| default : |
| break; |
| } |
| |
| if ((nflags & IPN_TCPUDP)) |
| tcp = fin->fin_dp; |
| } |
| |
| ipa = fin->fin_src6; |
| |
| READ_ENTER(&softc->ipf_nat); |
| |
| if ((fin->fin_p == IPPROTO_ICMPV6) && !(nflags & IPN_ICMPQUERY) && |
| (nat = ipf_nat6_icmperror(fin, &nflags, NAT_OUTBOUND))) |
| /*EMPTY*/; |
| else if ((fin->fin_flx & FI_FRAG) && (nat = ipf_frag_natknown(fin))) |
| natadd = 0; |
| else if ((nat = ipf_nat6_outlookup(fin, nflags|NAT_SEARCH, |
| (u_int)fin->fin_p, |
| &fin->fin_src6.in6, |
| &fin->fin_dst6.in6))) { |
| nflags = nat->nat_flags; |
| } else if (fin->fin_off == 0) { |
| u_32_t hv, nmsk = 0; |
| i6addr_t *msk; |
| |
| /* |
| * If there is no current entry in the nat table for this IP#, |
| * create one for it (if there is a matching rule). |
| */ |
| maskloop: |
| msk = &softn->ipf_nat6_map_active_masks[nmsk]; |
| IP6_AND(&ipa, msk, &iph); |
| hv = NAT_HASH_FN6(&iph, 0, softn->ipf_nat_maprules_sz); |
| for (np = softn->ipf_nat_map_rules[hv]; np; np = np->in_mnext) { |
| if ((np->in_ifps[1] && (np->in_ifps[1] != ifp))) |
| continue; |
| if (np->in_v[0] != 6) |
| continue; |
| if (np->in_pr[1] && (np->in_pr[1] != fin->fin_p)) |
| continue; |
| if ((np->in_flags & IPN_RF) && |
| !(np->in_flags & nflags)) |
| continue; |
| if (np->in_flags & IPN_FILTER) { |
| switch (ipf_nat6_match(fin, np)) |
| { |
| case 0 : |
| continue; |
| case -1 : |
| rval = -1; |
| goto outmatchfail; |
| case 1 : |
| default : |
| break; |
| } |
| } else if (!IP6_MASKEQ(&ipa, &np->in_osrcmsk, |
| &np->in_osrcip6)) |
| continue; |
| |
| if ((fr != NULL) && |
| !ipf_matchtag(&np->in_tag, &fr->fr_nattag)) |
| continue; |
| |
| if (np->in_plabel != -1) { |
| if (((np->in_flags & IPN_FILTER) == 0) && |
| (np->in_odport != fin->fin_data[1])) |
| continue; |
| if (ipf_proxy_ok(fin, tcp, np) == 0) |
| continue; |
| } |
| |
| if (np->in_flags & IPN_NO) { |
| np->in_hits++; |
| break; |
| } |
| |
| MUTEX_ENTER(&softn->ipf_nat_new); |
| nat = ipf_nat6_add(fin, np, NULL, nflags, NAT_OUTBOUND); |
| MUTEX_EXIT(&softn->ipf_nat_new); |
| if (nat != NULL) { |
| np->in_hits++; |
| break; |
| } |
| natfailed = -1; |
| } |
| if ((np == NULL) && (nmsk < softn->ipf_nat6_map_max)) { |
| nmsk++; |
| goto maskloop; |
| } |
| } |
| |
| if (nat != NULL) { |
| rval = ipf_nat6_out(fin, nat, natadd, nflags); |
| if (rval == 1) { |
| MUTEX_ENTER(&nat->nat_lock); |
| ipf_nat_update(fin, nat); |
| nat->nat_bytes[1] += fin->fin_plen; |
| nat->nat_pkts[1]++; |
| MUTEX_EXIT(&nat->nat_lock); |
| } |
| } else |
| rval = natfailed; |
| outmatchfail: |
| RWLOCK_EXIT(&softc->ipf_nat); |
| |
| switch (rval) |
| { |
| case -1 : |
| if (passp != NULL) { |
| NBUMPSIDE6D(1, ns_drop); |
| *passp = FR_BLOCK; |
| fin->fin_reason = FRB_NATV6; |
| } |
| fin->fin_flx |= FI_BADNAT; |
| NBUMPSIDE6D(1, ns_badnat); |
| break; |
| case 0 : |
| NBUMPSIDE6D(1, ns_ignored); |
| break; |
| case 1 : |
| NBUMPSIDE6D(1, ns_translated); |
| break; |
| } |
| fin->fin_ifp = sifp; |
| return rval; |
| } |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_out */ |
| /* Returns: int - -1 == packet failed NAT checks so block it, */ |
| /* 1 == packet was successfully translated. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT structure */ |
| /* natadd(I) - flag indicating if it is safe to add frag cache */ |
| /* nflags(I) - NAT flags set for this packet */ |
| /* */ |
| /* Translate a packet coming "out" on an interface. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_out(fin, nat, natadd, nflags) |
| fr_info_t *fin; |
| nat_t *nat; |
| int natadd; |
| u_32_t nflags; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| struct icmp6_hdr *icmp6; |
| tcphdr_t *tcp; |
| ipnat_t *np; |
| int skip; |
| int i; |
| |
| tcp = NULL; |
| icmp6 = NULL; |
| np = nat->nat_ptr; |
| |
| if ((natadd != 0) && (fin->fin_flx & FI_FRAG) && (np != NULL)) |
| (void) ipf_frag_natnew(softc, fin, 0, nat); |
| |
| /* |
| * Address assignment is after the checksum modification because |
| * we are using the address in the packet for determining the |
| * correct checksum offset (the ICMP error could be coming from |
| * anyone...) |
| */ |
| switch (nat->nat_dir) |
| { |
| case NAT_OUTBOUND : |
| fin->fin_ip6->ip6_src = nat->nat_nsrc6.in6; |
| fin->fin_src6 = nat->nat_nsrc6; |
| fin->fin_ip6->ip6_dst = nat->nat_ndst6.in6; |
| fin->fin_dst6 = nat->nat_ndst6; |
| break; |
| |
| case NAT_INBOUND : |
| fin->fin_ip6->ip6_src = nat->nat_odst6.in6; |
| fin->fin_src6 = nat->nat_ndst6; |
| fin->fin_ip6->ip6_dst = nat->nat_osrc6.in6; |
| fin->fin_dst6 = nat->nat_nsrc6; |
| break; |
| |
| case NAT_DIVERTIN : |
| { |
| mb_t *m; |
| |
| skip = ipf_nat6_decap(fin, nat); |
| if (skip <= 0) { |
| NBUMPSIDE6D(1, ns_decap_fail); |
| return -1; |
| } |
| |
| m = fin->fin_m; |
| |
| #if defined(MENTAT) && defined(_KERNEL) |
| m->b_rptr += skip; |
| #else |
| m->m_data += skip; |
| m->m_len -= skip; |
| |
| # ifdef M_PKTHDR |
| if (m->m_flags & M_PKTHDR) |
| m->m_pkthdr.len -= skip; |
| # endif |
| #endif |
| |
| MUTEX_ENTER(&nat->nat_lock); |
| ipf_nat_update(fin, nat); |
| MUTEX_EXIT(&nat->nat_lock); |
| fin->fin_flx |= FI_NATED; |
| if (np != NULL && np->in_tag.ipt_num[0] != 0) |
| fin->fin_nattag = &np->in_tag; |
| return 1; |
| /* NOTREACHED */ |
| } |
| |
| case NAT_DIVERTOUT : |
| { |
| udphdr_t *uh; |
| ip6_t *ip6; |
| mb_t *m; |
| |
| m = M_DUP(np->in_divmp); |
| if (m == NULL) { |
| NBUMPSIDE6D(1, ns_divert_dup); |
| return -1; |
| } |
| |
| ip6 = MTOD(m, ip6_t *); |
| |
| ip6->ip6_plen = htons(fin->fin_plen + 8); |
| |
| uh = (udphdr_t *)(ip6 + 1); |
| uh->uh_ulen = htons(fin->fin_plen); |
| |
| PREP_MB_T(fin, m); |
| |
| fin->fin_ip6 = ip6; |
| fin->fin_plen += sizeof(ip6_t) + 8; /* UDP + new IPv4 hdr */ |
| fin->fin_dlen += sizeof(ip6_t) + 8; /* UDP + old IPv4 hdr */ |
| |
| nflags &= ~IPN_TCPUDPICMP; |
| |
| break; |
| } |
| |
| default : |
| break; |
| } |
| |
| if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) { |
| u_short *csump; |
| |
| if ((nat->nat_nsport != 0) && (nflags & IPN_TCPUDP)) { |
| tcp = fin->fin_dp; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_OUTBOUND : |
| tcp->th_sport = nat->nat_nsport; |
| fin->fin_data[0] = ntohs(nat->nat_nsport); |
| tcp->th_dport = nat->nat_ndport; |
| fin->fin_data[1] = ntohs(nat->nat_ndport); |
| break; |
| |
| case NAT_INBOUND : |
| tcp->th_sport = nat->nat_odport; |
| fin->fin_data[0] = ntohs(nat->nat_odport); |
| tcp->th_dport = nat->nat_osport; |
| fin->fin_data[1] = ntohs(nat->nat_osport); |
| break; |
| } |
| } |
| |
| if ((nat->nat_nicmpid != 0) && (nflags & IPN_ICMPQUERY)) { |
| icmp6 = fin->fin_dp; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_OUTBOUND : |
| icmp6->icmp6_id = nat->nat_nicmpid; |
| break; |
| case NAT_INBOUND : |
| icmp6->icmp6_id = nat->nat_oicmpid; |
| break; |
| } |
| } |
| |
| csump = ipf_nat_proto(fin, nat, nflags); |
| |
| /* |
| * The above comments do not hold for layer 4 (or higher) |
| * checksums... |
| */ |
| if (csump != NULL) { |
| if (nat->nat_dir == NAT_OUTBOUND) |
| ipf_fix_outcksum(fin->fin_cksum, csump, |
| nat->nat_sumd[0], |
| nat->nat_sumd[1] + |
| fin->fin_dlen); |
| else |
| ipf_fix_incksum(fin->fin_cksum, csump, |
| nat->nat_sumd[0], |
| nat->nat_sumd[1] + |
| fin->fin_dlen); |
| } |
| } |
| |
| ipf_sync_update(softc, SMC_NAT, fin, nat->nat_sync); |
| /* ------------------------------------------------------------- */ |
| /* A few quick notes: */ |
| /* Following are test conditions prior to calling the */ |
| /* ipf_proxy_check routine. */ |
| /* */ |
| /* A NULL tcp indicates a non TCP/UDP packet. When dealing */ |
| /* with a redirect rule, we attempt to match the packet's */ |
| /* source port against in_dport, otherwise we'd compare the */ |
| /* packet's destination. */ |
| /* ------------------------------------------------------------- */ |
| if ((np != NULL) && (np->in_apr != NULL)) { |
| i = ipf_proxy_check(fin, nat); |
| if (i == 0) { |
| i = 1; |
| } else if (i == -1) { |
| NBUMPSIDE6D(1, ns_ipf_proxy_fail); |
| } |
| } else { |
| i = 1; |
| } |
| fin->fin_flx |= FI_NATED; |
| return i; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_checkin */ |
| /* Returns: int - -1 == packet failed NAT checks so block it, */ |
| /* 0 == no packet translation occurred, */ |
| /* 1 == packet was successfully translated. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* passp(I) - pointer to filtering result flags */ |
| /* */ |
| /* Check to see if an incoming packet should be changed. ICMP packets are */ |
| /* first checked to see if they match an existing entry (if an error), */ |
| /* otherwise a search of the current NAT table is made. If neither results */ |
| /* in a match then a search for a matching NAT rule is made. Create a new */ |
| /* NAT entry if a we matched a NAT rule. Lastly, actually change the */ |
| /* packet header(s) as required. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_checkin(fin, passp) |
| fr_info_t *fin; |
| u_32_t *passp; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| struct icmp6_hdr *icmp6; |
| u_int nflags, natadd; |
| int rval, natfailed; |
| struct ifnet *ifp; |
| i6addr_t ipa, iph; |
| tcphdr_t *tcp; |
| u_short dport; |
| ipnat_t *np; |
| nat_t *nat; |
| |
| if (softn->ipf_nat_stats.ns_rules == 0 || softn->ipf_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; |
| |
| /* |
| * Apart from ECHO request and reply, all other |
| * informational messages should not be translated |
| * so as to keep IPv6 working. |
| */ |
| if (icmp6->icmp6_type > ICMP6_ECHO_REPLY) |
| return 0; |
| |
| /* |
| * This is an incoming packet, so the destination is |
| * the icmp6_id and the source port equals 0 |
| */ |
| 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 = fin->fin_data[1]; |
| } |
| } |
| |
| ipa = fin->fin_dst6; |
| |
| READ_ENTER(&softc->ipf_nat); |
| |
| if ((fin->fin_p == IPPROTO_ICMPV6) && !(nflags & IPN_ICMPQUERY) && |
| (nat = ipf_nat6_icmperror(fin, &nflags, NAT_INBOUND))) |
| /*EMPTY*/; |
| else if ((fin->fin_flx & FI_FRAG) && (nat = ipf_frag_natknown(fin))) |
| natadd = 0; |
| else if ((nat = ipf_nat6_inlookup(fin, nflags|NAT_SEARCH, |
| (u_int)fin->fin_p, |
| &fin->fin_src6.in6, &ipa.in6))) { |
| nflags = nat->nat_flags; |
| } else if (fin->fin_off == 0) { |
| u_32_t hv, rmsk = 0; |
| i6addr_t *msk; |
| |
| /* |
| * If there is no current entry in the nat table for this IP#, |
| * create one for it (if there is a matching rule). |
| */ |
| maskloop: |
| msk = &softn->ipf_nat6_rdr_active_masks[rmsk]; |
| IP6_AND(&ipa, msk, &iph); |
| hv = NAT_HASH_FN6(&iph, 0, softn->ipf_nat_rdrrules_sz); |
| for (np = softn->ipf_nat_rdr_rules[hv]; np; np = np->in_rnext) { |
| if (np->in_ifps[0] && (np->in_ifps[0] != ifp)) |
| continue; |
| if (np->in_v[0] != 6) |
| continue; |
| if (np->in_pr[0] && (np->in_pr[0] != fin->fin_p)) |
| continue; |
| if ((np->in_flags & IPN_RF) && !(np->in_flags & nflags)) |
| continue; |
| if (np->in_flags & IPN_FILTER) { |
| switch (ipf_nat6_match(fin, np)) |
| { |
| case 0 : |
| continue; |
| case -1 : |
| rval = -1; |
| goto inmatchfail; |
| case 1 : |
| default : |
| break; |
| } |
| } else { |
| if (!IP6_MASKEQ(&ipa, &np->in_odstmsk6, |
| &np->in_odstip6)) { |
| continue; |
| } |
| if (np->in_odport && |
| ((np->in_dtop < dport) || |
| (dport < np->in_odport))) |
| continue; |
| } |
| |
| if (np->in_plabel != -1) { |
| if (!ipf_proxy_ok(fin, tcp, np)) { |
| continue; |
| } |
| } |
| |
| if (np->in_flags & IPN_NO) { |
| np->in_hits++; |
| break; |
| } |
| |
| MUTEX_ENTER(&softn->ipf_nat_new); |
| nat = ipf_nat6_add(fin, np, NULL, nflags, NAT_INBOUND); |
| MUTEX_EXIT(&softn->ipf_nat_new); |
| if (nat != NULL) { |
| np->in_hits++; |
| break; |
| } |
| natfailed = -1; |
| } |
| |
| if ((np == NULL) && (rmsk < softn->ipf_nat6_rdr_max)) { |
| rmsk++; |
| goto maskloop; |
| } |
| } |
| if (nat != NULL) { |
| rval = ipf_nat6_in(fin, nat, natadd, nflags); |
| if (rval == 1) { |
| MUTEX_ENTER(&nat->nat_lock); |
| ipf_nat_update(fin, nat); |
| nat->nat_bytes[0] += fin->fin_plen; |
| nat->nat_pkts[0]++; |
| MUTEX_EXIT(&nat->nat_lock); |
| } |
| } else |
| rval = natfailed; |
| inmatchfail: |
| RWLOCK_EXIT(&softc->ipf_nat); |
| |
| switch (rval) |
| { |
| case -1 : |
| if (passp != NULL) { |
| NBUMPSIDE6D(0, ns_drop); |
| *passp = FR_BLOCK; |
| fin->fin_reason = FRB_NATV6; |
| } |
| fin->fin_flx |= FI_BADNAT; |
| NBUMPSIDE6D(0, ns_badnat); |
| break; |
| case 0 : |
| NBUMPSIDE6D(0, ns_ignored); |
| break; |
| case 1 : |
| NBUMPSIDE6D(0, ns_translated); |
| break; |
| } |
| return rval; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_in */ |
| /* Returns: int - -1 == packet failed NAT checks so block it, */ |
| /* 1 == packet was successfully translated. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT structure */ |
| /* natadd(I) - flag indicating if it is safe to add frag cache */ |
| /* nflags(I) - NAT flags set for this packet */ |
| /* Locks Held: (READ) */ |
| /* */ |
| /* Translate a packet coming "in" on an interface. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_in(fin, nat, natadd, nflags) |
| fr_info_t *fin; |
| nat_t *nat; |
| int natadd; |
| u_32_t nflags; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| struct icmp6_hdr *icmp6; |
| u_short *csump; |
| tcphdr_t *tcp; |
| ipnat_t *np; |
| int skip; |
| int i; |
| |
| tcp = NULL; |
| csump = NULL; |
| np = nat->nat_ptr; |
| fin->fin_fr = nat->nat_fr; |
| |
| if (np != NULL) { |
| if ((natadd != 0) && (fin->fin_flx & FI_FRAG)) |
| (void) ipf_frag_natnew(softc, fin, 0, nat); |
| |
| /* ------------------------------------------------------------- */ |
| /* A few quick notes: */ |
| /* Following are test conditions prior to calling the */ |
| /* ipf_proxy_check routine. */ |
| /* */ |
| /* A NULL tcp indicates a non TCP/UDP packet. When dealing */ |
| /* with a map rule, we attempt to match the packet's */ |
| /* source port against in_dport, otherwise we'd compare the */ |
| /* packet's destination. */ |
| /* ------------------------------------------------------------- */ |
| if (np->in_apr != NULL) { |
| i = ipf_proxy_check(fin, nat); |
| if (i == -1) { |
| NBUMPSIDE6D(0, ns_ipf_proxy_fail); |
| return -1; |
| } |
| } |
| } |
| |
| ipf_sync_update(softc, SMC_NAT, fin, nat->nat_sync); |
| |
| /* |
| * Fix up checksums, not by recalculating them, but |
| * simply computing adjustments. |
| * Why only do this for some platforms on inbound packets ? |
| * Because for those that it is done, IP processing is yet to happen |
| * and so the IPv4 header checksum has not yet been evaluated. |
| * Perhaps it should always be done for the benefit of things like |
| * fast forwarding (so that it doesn't need to be recomputed) but with |
| * header checksum offloading, perhaps it is a moot point. |
| */ |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| if ((fin->fin_flx & FI_ICMPERR) == 0) { |
| fin->fin_ip6->ip6_src = nat->nat_nsrc6.in6; |
| fin->fin_src6 = nat->nat_nsrc6; |
| } |
| fin->fin_ip6->ip6_dst = nat->nat_ndst6.in6; |
| fin->fin_dst6 = nat->nat_ndst6; |
| break; |
| |
| case NAT_OUTBOUND : |
| if ((fin->fin_flx & FI_ICMPERR) == 0) { |
| fin->fin_ip6->ip6_src = nat->nat_odst6.in6; |
| fin->fin_src6 = nat->nat_odst6; |
| } |
| fin->fin_ip6->ip6_dst = nat->nat_osrc6.in6; |
| fin->fin_dst6 = nat->nat_osrc6; |
| break; |
| |
| case NAT_DIVERTIN : |
| { |
| udphdr_t *uh; |
| ip6_t *ip6; |
| mb_t *m; |
| |
| m = M_DUP(np->in_divmp); |
| if (m == NULL) { |
| NBUMPSIDE6D(0, ns_divert_dup); |
| return -1; |
| } |
| |
| ip6 = MTOD(m, ip6_t *); |
| ip6->ip6_plen = htons(fin->fin_plen + sizeof(udphdr_t)); |
| |
| uh = (udphdr_t *)(ip6 + 1); |
| uh->uh_ulen = ntohs(fin->fin_plen); |
| |
| PREP_MB_T(fin, m); |
| |
| fin->fin_ip6 = ip6; |
| fin->fin_plen += sizeof(ip6_t) + 8; /* UDP + new IPv6 hdr */ |
| fin->fin_dlen += sizeof(ip6_t) + 8; /* UDP + old IPv6 hdr */ |
| |
| nflags &= ~IPN_TCPUDPICMP; |
| |
| break; |
| } |
| |
| case NAT_DIVERTOUT : |
| { |
| mb_t *m; |
| |
| skip = ipf_nat6_decap(fin, nat); |
| if (skip <= 0) { |
| NBUMPSIDE6D(0, ns_decap_fail); |
| return -1; |
| } |
| |
| m = fin->fin_m; |
| |
| #if defined(MENTAT) && defined(_KERNEL) |
| m->b_rptr += skip; |
| #else |
| m->m_data += skip; |
| m->m_len -= skip; |
| |
| # ifdef M_PKTHDR |
| if (m->m_flags & M_PKTHDR) |
| m->m_pkthdr.len -= skip; |
| # endif |
| #endif |
| |
| ipf_nat_update(fin, nat); |
| fin->fin_flx |= FI_NATED; |
| if (np != NULL && np->in_tag.ipt_num[0] != 0) |
| fin->fin_nattag = &np->in_tag; |
| return 1; |
| /* NOTREACHED */ |
| } |
| } |
| if (nflags & IPN_TCPUDP) |
| tcp = fin->fin_dp; |
| |
| if (!(fin->fin_flx & FI_SHORT) && (fin->fin_off == 0)) { |
| if ((nat->nat_odport != 0) && (nflags & IPN_TCPUDP)) { |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| tcp->th_sport = nat->nat_nsport; |
| fin->fin_data[0] = ntohs(nat->nat_nsport); |
| tcp->th_dport = nat->nat_ndport; |
| fin->fin_data[1] = ntohs(nat->nat_ndport); |
| break; |
| |
| case NAT_OUTBOUND : |
| tcp->th_sport = nat->nat_odport; |
| fin->fin_data[0] = ntohs(nat->nat_odport); |
| tcp->th_dport = nat->nat_osport; |
| fin->fin_data[1] = ntohs(nat->nat_osport); |
| break; |
| } |
| } |
| |
| |
| if ((nat->nat_nicmpid != 0) && (nflags & IPN_ICMPQUERY)) { |
| icmp6 = fin->fin_dp; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_INBOUND : |
| icmp6->icmp6_id = nat->nat_nicmpid; |
| break; |
| case NAT_OUTBOUND : |
| icmp6->icmp6_id = nat->nat_oicmpid; |
| break; |
| } |
| } |
| |
| csump = ipf_nat_proto(fin, nat, nflags); |
| } |
| |
| /* |
| * The above comments do not hold for layer 4 (or higher) checksums... |
| */ |
| if (csump != NULL) { |
| if (nat->nat_dir == NAT_OUTBOUND) |
| ipf_fix_incksum(0, csump, nat->nat_sumd[0], 0); |
| else |
| ipf_fix_outcksum(0, csump, nat->nat_sumd[0], 0); |
| } |
| fin->fin_flx |= FI_NATED; |
| if (np != NULL && np->in_tag.ipt_num[0] != 0) |
| fin->fin_nattag = &np->in_tag; |
| return 1; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_newrewrite */ |
| /* Returns: int - -1 == error, 0 == success (no move), 1 == success and */ |
| /* allow rule to be moved if IPN_ROUNDR is set. */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT entry */ |
| /* ni(I) - pointer to structure with misc. information needed */ |
| /* to create new NAT entry. */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* This function is responsible for setting up an active NAT session where */ |
| /* we are changing both the source and destination parameters at the same */ |
| /* time. The loop in here works differently to elsewhere - each iteration */ |
| /* is responsible for changing a single parameter that can be incremented. */ |
| /* So one pass may increase the source IP#, next source port, next dest. IP#*/ |
| /* and the last destination port for a total of 4 iterations to try each. */ |
| /* This is done to try and exhaustively use the translation space available.*/ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_newrewrite(fin, nat, nai) |
| fr_info_t *fin; |
| nat_t *nat; |
| natinfo_t *nai; |
| { |
| int src_search = 1; |
| int dst_search = 1; |
| fr_info_t frnat; |
| u_32_t flags; |
| u_short swap; |
| ipnat_t *np; |
| nat_t *natl; |
| int l = 0; |
| int changed; |
| |
| natl = NULL; |
| changed = -1; |
| np = nai->nai_np; |
| flags = nat->nat_flags; |
| bcopy((char *)fin, (char *)&frnat, sizeof(*fin)); |
| |
| nat->nat_hm = NULL; |
| |
| do { |
| changed = -1; |
| /* TRACE (l, src_search, dst_search, np) */ |
| |
| if ((src_search == 0) && (np->in_spnext == 0) && |
| (dst_search == 0) && (np->in_dpnext == 0)) { |
| if (l > 0) |
| return -1; |
| } |
| |
| /* |
| * Find a new source address |
| */ |
| if (ipf_nat6_nextaddr(fin, &np->in_nsrc, &frnat.fin_src6, |
| &frnat.fin_src6) == -1) { |
| return -1; |
| } |
| |
| if (IP6_ISZERO(&np->in_nsrcip6) && |
| IP6_ISONES(&np->in_nsrcmsk6)) { |
| src_search = 0; |
| if (np->in_stepnext == 0) |
| np->in_stepnext = 1; |
| |
| } else if (IP6_ISZERO(&np->in_nsrcip6) && |
| IP6_ISZERO(&np->in_nsrcmsk6)) { |
| src_search = 0; |
| if (np->in_stepnext == 0) |
| np->in_stepnext = 1; |
| |
| } else if (IP6_ISONES(&np->in_nsrcmsk)) { |
| src_search = 0; |
| if (np->in_stepnext == 0) |
| np->in_stepnext = 1; |
| |
| } else if (!IP6_ISONES(&np->in_nsrcmsk6)) { |
| if (np->in_stepnext == 0 && changed == -1) { |
| IP6_INC(&np->in_snip); |
| np->in_stepnext++; |
| changed = 0; |
| } |
| } |
| |
| if ((flags & IPN_TCPUDPICMP) != 0) { |
| if (np->in_spnext != 0) |
| frnat.fin_data[0] = np->in_spnext; |
| |
| /* |
| * Standard port translation. Select next port. |
| */ |
| if ((flags & IPN_FIXEDSPORT) != 0) { |
| np->in_stepnext = 2; |
| } else if ((np->in_stepnext == 1) && |
| (changed == -1) && (natl != NULL)) { |
| np->in_spnext++; |
| np->in_stepnext++; |
| changed = 1; |
| if (np->in_spnext > np->in_spmax) |
| np->in_spnext = np->in_spmin; |
| } |
| } else { |
| np->in_stepnext = 2; |
| } |
| np->in_stepnext &= 0x3; |
| |
| /* |
| * Find a new destination address |
| */ |
| /* TRACE (fin, np, l, frnat) */ |
| |
| if (ipf_nat6_nextaddr(fin, &np->in_ndst, &frnat.fin_dst6, |
| &frnat.fin_dst6) == -1) |
| return -1; |
| |
| if (IP6_ISZERO(&np->in_ndstip6) && |
| IP6_ISONES(&np->in_ndstmsk6)) { |
| dst_search = 0; |
| if (np->in_stepnext == 2) |
| np->in_stepnext = 3; |
| |
| } else if (IP6_ISZERO(&np->in_ndstip6) && |
| IP6_ISZERO(&np->in_ndstmsk6)) { |
| dst_search = 0; |
| if (np->in_stepnext == 2) |
| np->in_stepnext = 3; |
| |
| } else if (IP6_ISONES(&np->in_ndstmsk6)) { |
| dst_search = 0; |
| if (np->in_stepnext == 2) |
| np->in_stepnext = 3; |
| |
| } else if (!IP6_ISONES(&np->in_ndstmsk6)) { |
| if ((np->in_stepnext == 2) && (changed == -1) && |
| (natl != NULL)) { |
| changed = 2; |
| np->in_stepnext++; |
| IP6_INC(&np->in_dnip6); |
| } |
| } |
| |
| if ((flags & IPN_TCPUDPICMP) != 0) { |
| if (np->in_dpnext != 0) |
| frnat.fin_data[1] = np->in_dpnext; |
| |
| /* |
| * Standard port translation. Select next port. |
| */ |
| if ((flags & IPN_FIXEDDPORT) != 0) { |
| np->in_stepnext = 0; |
| } else if (np->in_stepnext == 3 && changed == -1) { |
| np->in_dpnext++; |
| np->in_stepnext++; |
| changed = 3; |
| if (np->in_dpnext > np->in_dpmax) |
| np->in_dpnext = np->in_dpmin; |
| } |
| } else { |
| if (np->in_stepnext == 3) |
| np->in_stepnext = 0; |
| } |
| |
| /* TRACE (frnat) */ |
| |
| /* |
| * Here we do a lookup of the connection as seen from |
| * the outside. If an IP# pair already exists, try |
| * again. So if you have A->B becomes C->B, you can |
| * also have D->E become C->E but not D->B causing |
| * another C->B. Also take protocol and ports into |
| * account when determining whether a pre-existing |
| * NAT setup will cause an external conflict where |
| * this is appropriate. |
| * |
| * fin_data[] is swapped around because we are doing a |
| * lookup of the packet is if it were moving in the opposite |
| * direction of the one we are working with now. |
| */ |
| if (flags & IPN_TCPUDP) { |
| swap = frnat.fin_data[0]; |
| frnat.fin_data[0] = frnat.fin_data[1]; |
| frnat.fin_data[1] = swap; |
| } |
| if (fin->fin_out == 1) { |
| natl = ipf_nat6_inlookup(&frnat, |
| flags & ~(SI_WILDP|NAT_SEARCH), |
| (u_int)frnat.fin_p, |
| &frnat.fin_dst6.in6, |
| &frnat.fin_src6.in6); |
| |
| } else { |
| natl = ipf_nat6_outlookup(&frnat, |
| flags & ~(SI_WILDP|NAT_SEARCH), |
| (u_int)frnat.fin_p, |
| &frnat.fin_dst6.in6, |
| &frnat.fin_src6.in6); |
| } |
| if (flags & IPN_TCPUDP) { |
| swap = frnat.fin_data[0]; |
| frnat.fin_data[0] = frnat.fin_data[1]; |
| frnat.fin_data[1] = swap; |
| } |
| |
| /* TRACE natl, in_stepnext, l */ |
| |
| if ((natl != NULL) && (l > 8)) /* XXX 8 is arbitrary */ |
| return -1; |
| |
| np->in_stepnext &= 0x3; |
| |
| l++; |
| changed = -1; |
| } while (natl != NULL); |
| nat->nat_osrc6 = fin->fin_src6; |
| nat->nat_odst6 = fin->fin_dst6; |
| nat->nat_nsrc6 = frnat.fin_src6; |
| nat->nat_ndst6 = frnat.fin_dst6; |
| |
| if ((flags & IPN_TCPUDP) != 0) { |
| nat->nat_osport = htons(fin->fin_data[0]); |
| nat->nat_odport = htons(fin->fin_data[1]); |
| nat->nat_nsport = htons(frnat.fin_data[0]); |
| nat->nat_ndport = htons(frnat.fin_data[1]); |
| } else if ((flags & IPN_ICMPQUERY) != 0) { |
| nat->nat_oicmpid = fin->fin_data[1]; |
| nat->nat_nicmpid = frnat.fin_data[1]; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_newdivert */ |
| /* Returns: int - -1 == error, 0 == success */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to NAT entry */ |
| /* ni(I) - pointer to structure with misc. information needed */ |
| /* to create new NAT entry. */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* Create a new NAT divert session as defined by the NAT rule. This is */ |
| /* somewhat different to other NAT session creation routines because we */ |
| /* do not iterate through either port numbers or IP addresses, searching */ |
| /* for a unique mapping, however, a complimentary duplicate check is made. */ |
| /* ------------------------------------------------------------------------ */ |
| int |
| ipf_nat6_newdivert(fin, nat, nai) |
| fr_info_t *fin; |
| nat_t *nat; |
| natinfo_t *nai; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| fr_info_t frnat; |
| ipnat_t *np; |
| nat_t *natl; |
| int p; |
| |
| np = nai->nai_np; |
| bcopy((char *)fin, (char *)&frnat, sizeof(*fin)); |
| |
| nat->nat_pr[0] = 0; |
| nat->nat_osrc6 = fin->fin_src6; |
| nat->nat_odst6 = fin->fin_dst6; |
| nat->nat_osport = htons(fin->fin_data[0]); |
| nat->nat_odport = htons(fin->fin_data[1]); |
| frnat.fin_src6 = np->in_snip6; |
| frnat.fin_dst6 = np->in_dnip6; |
| |
| if (np->in_redir & NAT_DIVERTUDP) { |
| frnat.fin_data[0] = np->in_spnext; |
| frnat.fin_data[1] = np->in_dpnext; |
| frnat.fin_flx |= FI_TCPUDP; |
| p = IPPROTO_UDP; |
| } else { |
| frnat.fin_flx &= ~FI_TCPUDP; |
| p = IPPROTO_IPIP; |
| } |
| |
| if (fin->fin_out == 1) { |
| natl = ipf_nat6_inlookup(&frnat, 0, p, &frnat.fin_dst6.in6, |
| &frnat.fin_src6.in6); |
| |
| } else { |
| natl = ipf_nat6_outlookup(&frnat, 0, p, &frnat.fin_dst6.in6, |
| &frnat.fin_src6.in6); |
| } |
| |
| if (natl != NULL) { |
| NBUMPSIDE6D(fin->fin_out, ns_divert_exist); |
| return -1; |
| } |
| |
| nat->nat_nsrc6 = frnat.fin_src6; |
| nat->nat_ndst6 = frnat.fin_dst6; |
| if (np->in_redir & NAT_DIVERTUDP) { |
| nat->nat_nsport = htons(frnat.fin_data[0]); |
| nat->nat_ndport = htons(frnat.fin_data[1]); |
| } |
| nat->nat_pr[fin->fin_out] = fin->fin_p; |
| nat->nat_pr[1 - fin->fin_out] = p; |
| |
| if (np->in_redir & NAT_REDIRECT) |
| nat->nat_dir = NAT_DIVERTIN; |
| else |
| nat->nat_dir = NAT_DIVERTOUT; |
| |
| return 0; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: nat6_builddivertmp */ |
| /* Returns: int - -1 == error, 0 == success */ |
| /* Parameters: np(I) - pointer to a NAT rule */ |
| /* */ |
| /* For divert rules, a skeleton packet representing what will be prepended */ |
| /* to the real packet is created. Even though we don't have the full */ |
| /* packet here, a checksum is calculated that we update later when we */ |
| /* fill in the final details. At present a 0 checksum for UDP is being set */ |
| /* here because it is expected that divert will be used for localhost. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_builddivertmp(softn, np) |
| ipf_nat_softc_t *softn; |
| ipnat_t *np; |
| { |
| udphdr_t *uh; |
| size_t len; |
| ip6_t *ip6; |
| |
| if ((np->in_redir & NAT_DIVERTUDP) != 0) |
| len = sizeof(ip6_t) + sizeof(udphdr_t); |
| else |
| len = sizeof(ip6_t); |
| |
| ALLOC_MB_T(np->in_divmp, len); |
| if (np->in_divmp == NULL) { |
| ATOMIC_INCL(softn->ipf_nat_stats.ns_divert_build); |
| return -1; |
| } |
| |
| /* |
| * First, the header to get the packet diverted to the new destination |
| */ |
| ip6 = MTOD(np->in_divmp, ip6_t *); |
| ip6->ip6_vfc = 0x60; |
| if ((np->in_redir & NAT_DIVERTUDP) != 0) |
| ip6->ip6_nxt = IPPROTO_UDP; |
| else |
| ip6->ip6_nxt = IPPROTO_IPIP; |
| ip6->ip6_hlim = 255; |
| ip6->ip6_plen = 0; |
| ip6->ip6_src = np->in_snip6.in6; |
| ip6->ip6_dst = np->in_dnip6.in6; |
| |
| if (np->in_redir & NAT_DIVERTUDP) { |
| uh = (udphdr_t *)((u_char *)ip6 + sizeof(*ip6)); |
| uh->uh_sum = 0; |
| uh->uh_ulen = 8; |
| uh->uh_sport = htons(np->in_spnext); |
| uh->uh_dport = htons(np->in_dpnext); |
| } |
| |
| return 0; |
| } |
| |
| |
| #define MINDECAP (sizeof(ip6_t) + sizeof(udphdr_t) + sizeof(ip6_t)) |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: nat6_decap */ |
| /* Returns: int - -1 == error, 0 == success */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* nat(I) - pointer to current NAT session */ |
| /* */ |
| /* This function is responsible for undoing a packet's encapsulation in the */ |
| /* reverse of an encap/divert rule. After removing the outer encapsulation */ |
| /* it is necessary to call ipf_makefrip() again so that the contents of 'fin'*/ |
| /* match the "new" packet as it may still be used by IPFilter elsewhere. */ |
| /* We use "dir" here as the basis for some of the expectations about the */ |
| /* outer header. If we return an error, the goal is to leave the original */ |
| /* packet information undisturbed - this falls short at the end where we'd */ |
| /* need to back a backup copy of "fin" - expensive. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_decap(fin, nat) |
| fr_info_t *fin; |
| nat_t *nat; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| char *hdr; |
| int skip; |
| mb_t *m; |
| |
| if ((fin->fin_flx & FI_ICMPERR) != 0) { |
| return 0; |
| } |
| |
| m = fin->fin_m; |
| skip = fin->fin_hlen; |
| |
| switch (nat->nat_dir) |
| { |
| case NAT_DIVERTIN : |
| case NAT_DIVERTOUT : |
| if (fin->fin_plen < MINDECAP) |
| return -1; |
| skip += sizeof(udphdr_t); |
| break; |
| |
| case NAT_ENCAPIN : |
| case NAT_ENCAPOUT : |
| if (fin->fin_plen < (skip + sizeof(ip6_t))) |
| return -1; |
| break; |
| default : |
| return -1; |
| /* NOTREACHED */ |
| } |
| |
| /* |
| * The aim here is to keep the original packet details in "fin" for |
| * as long as possible so that returning with an error is for the |
| * original packet and there is little undoing work to do. |
| */ |
| if (M_LEN(m) < skip + sizeof(ip6_t)) { |
| if (ipf_pr_pullup(fin, skip + sizeof(ip6_t)) == -1) |
| return -1; |
| } |
| |
| hdr = MTOD(fin->fin_m, char *); |
| fin->fin_ip6 = (ip6_t *)(hdr + skip); |
| |
| if (ipf_pr_pullup(fin, skip + sizeof(ip6_t)) == -1) { |
| NBUMPSIDE6D(fin->fin_out, ns_decap_pullup); |
| return -1; |
| } |
| |
| fin->fin_hlen = sizeof(ip6_t); |
| fin->fin_dlen -= skip; |
| fin->fin_plen -= skip; |
| fin->fin_ipoff += skip; |
| |
| if (ipf_makefrip(sizeof(ip6_t), (ip_t *)hdr, fin) == -1) { |
| NBUMPSIDE6D(fin->fin_out, ns_decap_bad); |
| return -1; |
| } |
| |
| return skip; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: nat6_nextaddr */ |
| /* Returns: int - -1 == bad input (no new address), */ |
| /* 0 == success and dst has new address */ |
| /* Parameters: fin(I) - pointer to packet information */ |
| /* na(I) - how to generate new address */ |
| /* old(I) - original address being replaced */ |
| /* dst(O) - where to put the new address */ |
| /* Write Lock: ipf_nat */ |
| /* */ |
| /* This function uses the contents of the "na" structure, in combination */ |
| /* with "old" to produce a new address to store in "dst". Not all of the */ |
| /* possible uses of "na" will result in a new address. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_nextaddr(fin, na, old, dst) |
| fr_info_t *fin; |
| nat_addr_t *na; |
| i6addr_t *old, *dst; |
| { |
| ipf_main_softc_t *softc = fin->fin_main_soft; |
| ipf_nat_softc_t *softn = softc->ipf_nat_soft; |
| i6addr_t newip, new; |
| u_32_t amin, amax; |
| int error; |
| |
| new.i6[0] = 0; |
| new.i6[1] = 0; |
| new.i6[2] = 0; |
| new.i6[3] = 0; |
| amin = na->na_addr[0].in4.s_addr; |
| |
| switch (na->na_atype) |
| { |
| case FRI_RANGE : |
| amax = na->na_addr[1].in4.s_addr; |
| break; |
| |
| case FRI_NETMASKED : |
| case FRI_DYNAMIC : |
| case FRI_NORMAL : |
| /* |
| * Compute the maximum address by adding the inverse of the |
| * netmask to the minimum address. |
| */ |
| amax = ~na->na_addr[1].in4.s_addr; |
| amax |= amin; |
| break; |
| |
| case FRI_LOOKUP : |
| break; |
| |
| case FRI_BROADCAST : |
| case FRI_PEERADDR : |
| case FRI_NETWORK : |
| default : |
| return -1; |
| } |
| |
| error = -1; |
| switch (na->na_function) |
| { |
| case IPLT_DSTLIST : |
| error = ipf_dstlist_select_node(fin, na->na_ptr, dst->i6, |
| NULL); |
| break; |
| |
| case IPLT_NONE : |
| /* |
| * 0/0 as the new address means leave it alone. |
| */ |
| if (na->na_addr[0].in4.s_addr == 0 && |
| na->na_addr[1].in4.s_addr == 0) { |
| new = *old; |
| |
| /* |
| * 0/32 means get the interface's address |
| */ |
| } else if (IP6_ISZERO(&na->na_addr[0].in6) && |
| IP6_ISONES(&na->na_addr[1].in6)) { |
| if (ipf_ifpaddr(softc, 6, na->na_atype, |
| fin->fin_ifp, &newip, NULL) == -1) { |
| NBUMPSIDE6(fin->fin_out, ns_ifpaddrfail); |
| return -1; |
| } |
| new = newip; |
| } else { |
| new.in6 = na->na_nextip6; |
| } |
| *dst = new; |
| error = 0; |
| break; |
| |
| default : |
| NBUMPSIDE6(fin->fin_out, ns_badnextaddr); |
| break; |
| } |
| |
| return error; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_nextaddrinit */ |
| /* Returns: int - 0 == success, else error number */ |
| /* Parameters: na(I) - NAT address information for generating new addr*/ |
| /* base(I) - start of where to find strings */ |
| /* initial(I) - flag indicating if it is the first call for */ |
| /* this "na" structure. */ |
| /* ifp(I) - network interface to derive address */ |
| /* information from. */ |
| /* */ |
| /* This function is expected to be called in two scenarious: when a new NAT */ |
| /* rule is loaded into the kernel and when the list of NAT rules is sync'd */ |
| /* up with the valid network interfaces (possibly due to them changing.) */ |
| /* To distinguish between these, the "initial" parameter is used. If it is */ |
| /* 1 then this indicates the rule has just been reloaded and 0 for when we */ |
| /* are updating information. This difference is important because in */ |
| /* instances where we are not updating address information associated with */ |
| /* a network interface, we don't want to disturb what the "next" address to */ |
| /* come out of ipf_nat6_nextaddr() will be. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_nextaddrinit(softc, base, na, initial, ifp) |
| ipf_main_softc_t *softc; |
| char *base; |
| nat_addr_t *na; |
| int initial; |
| void *ifp; |
| { |
| switch (na->na_atype) |
| { |
| case FRI_LOOKUP : |
| if (na->na_subtype == 0) { |
| na->na_ptr = ipf_lookup_res_num(softc, IPL_LOGNAT, |
| na->na_type, |
| na->na_num, |
| &na->na_func); |
| } else if (na->na_subtype == 1) { |
| na->na_ptr = ipf_lookup_res_name(softc, IPL_LOGNAT, |
| na->na_type, |
| base + na->na_num, |
| &na->na_func); |
| } |
| if (na->na_func == NULL) { |
| IPFERROR(60072); |
| return ESRCH; |
| } |
| if (na->na_ptr == NULL) { |
| IPFERROR(60073); |
| return ESRCH; |
| } |
| break; |
| case FRI_DYNAMIC : |
| case FRI_BROADCAST : |
| case FRI_NETWORK : |
| case FRI_NETMASKED : |
| case FRI_PEERADDR : |
| if (ifp != NULL) |
| (void )ipf_ifpaddr(softc, 6, na->na_atype, ifp, |
| &na->na_addr[0], |
| &na->na_addr[1]); |
| break; |
| |
| case FRI_SPLIT : |
| case FRI_RANGE : |
| if (initial) |
| na->na_nextip6 = na->na_addr[0].in6; |
| break; |
| |
| case FRI_NONE : |
| IP6_ANDASSIGN(&na->na_addr[0].in6, &na->na_addr[1].in6); |
| return 0; |
| |
| case FRI_NORMAL : |
| IP6_ANDASSIGN(&na->na_addr[0].in6, &na->na_addr[1].in6); |
| break; |
| |
| default : |
| IPFERROR(60074); |
| return EINVAL; |
| } |
| |
| if (initial && (na->na_atype == FRI_NORMAL)) { |
| if (IP6_ISZERO(&na->na_addr[0].in6)) { |
| if (IP6_ISONES(&na->na_addr[1].in6) || |
| IP6_ISZERO(&na->na_addr[1].in6)) { |
| return 0; |
| } |
| } |
| |
| na->na_nextip6 = na->na_addr[0].in6; |
| if (!IP6_ISONES(&na->na_addr[1].in6)) { |
| IP6_INC(&na->na_nextip6); |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /* ------------------------------------------------------------------------ */ |
| /* Function: ipf_nat6_icmpquerytype */ |
| /* Returns: int - 1 == success, 0 == failure */ |
| /* Parameters: icmptype(I) - ICMP type number */ |
| /* */ |
| /* Tests to see if the ICMP type number passed is a query/response type or */ |
| /* not. */ |
| /* ------------------------------------------------------------------------ */ |
| static int |
| ipf_nat6_icmpquerytype(icmptype) |
| int icmptype; |
| { |
| |
| /* |
| * For the ICMP query NAT code, it is essential that both the query |
| * and the reply match on the NAT rule. Because the NAT structure |
| * does not keep track of the icmptype, and a single NAT structure |
| * is used for all icmp types with the same src, dest and id, we |
| * simply define the replies as queries as well. The funny thing is, |
| * altough it seems silly to call a reply a query, this is exactly |
| * as it is defined in the IPv4 specification |
| */ |
| |
| switch (icmptype) |
| { |
| |
| case ICMP6_ECHO_REPLY: |
| case ICMP6_ECHO_REQUEST: |
| /* route aedvertisement/solliciation is currently unsupported: */ |
| /* it would require rewriting the ICMP data section */ |
| 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; |
| } |
| } |
| #endif /* USE_INET6 */ |