xref: /onnv-gate/usr/src/uts/common/inet/iptun/iptun.c (revision 11042:2d6e217af1b4)
110616SSebastien.Roy@Sun.COM /*
210616SSebastien.Roy@Sun.COM  * CDDL HEADER START
310616SSebastien.Roy@Sun.COM  *
410616SSebastien.Roy@Sun.COM  * The contents of this file are subject to the terms of the
510616SSebastien.Roy@Sun.COM  * Common Development and Distribution License (the "License").
610616SSebastien.Roy@Sun.COM  * You may not use this file except in compliance with the License.
710616SSebastien.Roy@Sun.COM  *
810616SSebastien.Roy@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
910616SSebastien.Roy@Sun.COM  * or http://www.opensolaris.org/os/licensing.
1010616SSebastien.Roy@Sun.COM  * See the License for the specific language governing permissions
1110616SSebastien.Roy@Sun.COM  * and limitations under the License.
1210616SSebastien.Roy@Sun.COM  *
1310616SSebastien.Roy@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
1410616SSebastien.Roy@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1510616SSebastien.Roy@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
1610616SSebastien.Roy@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
1710616SSebastien.Roy@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
1810616SSebastien.Roy@Sun.COM  *
1910616SSebastien.Roy@Sun.COM  * CDDL HEADER END
2010616SSebastien.Roy@Sun.COM  */
2110616SSebastien.Roy@Sun.COM /*
2210616SSebastien.Roy@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
2310616SSebastien.Roy@Sun.COM  * Use is subject to license terms.
2410616SSebastien.Roy@Sun.COM  */
2510616SSebastien.Roy@Sun.COM 
2610616SSebastien.Roy@Sun.COM /*
2710616SSebastien.Roy@Sun.COM  * iptun - IP Tunneling Driver
2810616SSebastien.Roy@Sun.COM  *
2910616SSebastien.Roy@Sun.COM  * This module is a GLDv3 driver that implements virtual datalinks over IP
3010616SSebastien.Roy@Sun.COM  * (a.k.a, IP tunneling).  The datalinks are managed through a dld ioctl
3110616SSebastien.Roy@Sun.COM  * interface (see iptun_ctl.c), and registered with GLDv3 using
3210616SSebastien.Roy@Sun.COM  * mac_register().  It implements the logic for various forms of IP (IPv4 or
3310616SSebastien.Roy@Sun.COM  * IPv6) encapsulation within IP (IPv4 or IPv6) by interacting with the ip
3410616SSebastien.Roy@Sun.COM  * module below it.  Each virtual IP tunnel datalink has a conn_t associated
3510616SSebastien.Roy@Sun.COM  * with it representing the "outer" IP connection.
3610616SSebastien.Roy@Sun.COM  *
3710616SSebastien.Roy@Sun.COM  * The module implements the following locking semantics:
3810616SSebastien.Roy@Sun.COM  *
3910616SSebastien.Roy@Sun.COM  * Lookups and deletions in iptun_hash are synchronized using iptun_hash_lock.
4010616SSebastien.Roy@Sun.COM  * See comments above iptun_hash_lock for details.
4110616SSebastien.Roy@Sun.COM  *
4210616SSebastien.Roy@Sun.COM  * No locks are ever held while calling up to GLDv3.  The general architecture
4310616SSebastien.Roy@Sun.COM  * of GLDv3 requires this, as the mac perimeter (essentially a lock) for a
4410616SSebastien.Roy@Sun.COM  * given link will be held while making downcalls (iptun_m_*() callbacks).
4510616SSebastien.Roy@Sun.COM  * Because we need to hold locks while handling downcalls, holding these locks
4610616SSebastien.Roy@Sun.COM  * while issuing upcalls results in deadlock scenarios.  See the block comment
4710616SSebastien.Roy@Sun.COM  * above iptun_task_cb() for details on how we safely issue upcalls without
4810616SSebastien.Roy@Sun.COM  * holding any locks.
4910616SSebastien.Roy@Sun.COM  *
5010616SSebastien.Roy@Sun.COM  * The contents of each iptun_t is protected by an iptun_mutex which is held
5110616SSebastien.Roy@Sun.COM  * in iptun_enter() (called by iptun_enter_by_linkid()), and exited in
5210616SSebastien.Roy@Sun.COM  * iptun_exit().
5310616SSebastien.Roy@Sun.COM  *
5410616SSebastien.Roy@Sun.COM  * See comments in iptun_delete() and iptun_free() for details on how the
5510616SSebastien.Roy@Sun.COM  * iptun_t is deleted safely.
5610616SSebastien.Roy@Sun.COM  */
5710616SSebastien.Roy@Sun.COM 
5810616SSebastien.Roy@Sun.COM #include <sys/types.h>
5910616SSebastien.Roy@Sun.COM #include <sys/kmem.h>
6010616SSebastien.Roy@Sun.COM #include <sys/errno.h>
6110616SSebastien.Roy@Sun.COM #include <sys/modhash.h>
6210616SSebastien.Roy@Sun.COM #include <sys/list.h>
6310616SSebastien.Roy@Sun.COM #include <sys/strsun.h>
6410616SSebastien.Roy@Sun.COM #include <sys/file.h>
6510616SSebastien.Roy@Sun.COM #include <sys/systm.h>
6610616SSebastien.Roy@Sun.COM #include <sys/tihdr.h>
6710616SSebastien.Roy@Sun.COM #include <sys/param.h>
6810616SSebastien.Roy@Sun.COM #include <sys/mac_provider.h>
6910616SSebastien.Roy@Sun.COM #include <sys/mac_ipv4.h>
7010616SSebastien.Roy@Sun.COM #include <sys/mac_ipv6.h>
7110616SSebastien.Roy@Sun.COM #include <sys/mac_6to4.h>
7210616SSebastien.Roy@Sun.COM #include <sys/tsol/tnet.h>
7310616SSebastien.Roy@Sun.COM #include <sys/sunldi.h>
7410616SSebastien.Roy@Sun.COM #include <netinet/in.h>
7510616SSebastien.Roy@Sun.COM #include <netinet/ip6.h>
7610616SSebastien.Roy@Sun.COM #include <inet/ip.h>
7710616SSebastien.Roy@Sun.COM #include <inet/ip_ire.h>
7810616SSebastien.Roy@Sun.COM #include <inet/ipsec_impl.h>
79*11042SErik.Nordmark@Sun.COM #include <sys/tsol/label.h>
80*11042SErik.Nordmark@Sun.COM #include <sys/tsol/tnet.h>
8110616SSebastien.Roy@Sun.COM #include <inet/iptun.h>
8210616SSebastien.Roy@Sun.COM #include "iptun_impl.h"
8310616SSebastien.Roy@Sun.COM 
8410616SSebastien.Roy@Sun.COM /* Do the tunnel type and address family match? */
8510616SSebastien.Roy@Sun.COM #define	IPTUN_ADDR_MATCH(iptun_type, family)				\
8610616SSebastien.Roy@Sun.COM 	((iptun_type == IPTUN_TYPE_IPV4 && family == AF_INET) ||	\
8710616SSebastien.Roy@Sun.COM 	(iptun_type == IPTUN_TYPE_IPV6 && family == AF_INET6) ||	\
8810616SSebastien.Roy@Sun.COM 	(iptun_type == IPTUN_TYPE_6TO4 && family == AF_INET))
8910616SSebastien.Roy@Sun.COM 
9010616SSebastien.Roy@Sun.COM #define	IPTUN_HASH_KEY(key)	((mod_hash_key_t)(uintptr_t)(key))
9110616SSebastien.Roy@Sun.COM 
9210616SSebastien.Roy@Sun.COM #define	IPTUN_MIN_IPV4_MTU	576		/* ip.h still uses 68 (!) */
9310616SSebastien.Roy@Sun.COM #define	IPTUN_MIN_IPV6_MTU	IPV6_MIN_MTU
9410616SSebastien.Roy@Sun.COM #define	IPTUN_MAX_IPV4_MTU	(IP_MAXPACKET - sizeof (ipha_t))
9510616SSebastien.Roy@Sun.COM #define	IPTUN_MAX_IPV6_MTU	(IP_MAXPACKET - sizeof (ip6_t) -	\
9610616SSebastien.Roy@Sun.COM 				    sizeof (iptun_encaplim_t))
9710616SSebastien.Roy@Sun.COM 
9810616SSebastien.Roy@Sun.COM #define	IPTUN_MIN_HOPLIMIT	1
9910616SSebastien.Roy@Sun.COM #define	IPTUN_MAX_HOPLIMIT	UINT8_MAX
10010616SSebastien.Roy@Sun.COM 
10110616SSebastien.Roy@Sun.COM #define	IPTUN_MIN_ENCAPLIMIT	0
10210616SSebastien.Roy@Sun.COM #define	IPTUN_MAX_ENCAPLIMIT	UINT8_MAX
10310616SSebastien.Roy@Sun.COM 
10410616SSebastien.Roy@Sun.COM #define	IPTUN_IPSEC_REQ_MASK	(IPSEC_PREF_REQUIRED | IPSEC_PREF_NEVER)
10510616SSebastien.Roy@Sun.COM 
10610616SSebastien.Roy@Sun.COM static iptun_encaplim_t	iptun_encaplim_init = {
10710616SSebastien.Roy@Sun.COM 	{ IPPROTO_NONE, 0 },
10810616SSebastien.Roy@Sun.COM 	IP6OPT_TUNNEL_LIMIT,
10910616SSebastien.Roy@Sun.COM 	1,
11010616SSebastien.Roy@Sun.COM 	IPTUN_DEFAULT_ENCAPLIMIT,	/* filled in with actual value later */
11110616SSebastien.Roy@Sun.COM 	IP6OPT_PADN,
11210616SSebastien.Roy@Sun.COM 	1,
11310616SSebastien.Roy@Sun.COM 	0
11410616SSebastien.Roy@Sun.COM };
11510616SSebastien.Roy@Sun.COM 
116*11042SErik.Nordmark@Sun.COM /*
117*11042SErik.Nordmark@Sun.COM  * Table containing per-iptun-type information.
118*11042SErik.Nordmark@Sun.COM  * Since IPv6 can run over all of these we have the IPv6 min as the min MTU.
119*11042SErik.Nordmark@Sun.COM  */
12010616SSebastien.Roy@Sun.COM static iptun_typeinfo_t	iptun_type_table[] = {
121*11042SErik.Nordmark@Sun.COM 	{ IPTUN_TYPE_IPV4, MAC_PLUGIN_IDENT_IPV4, IPV4_VERSION,
122*11042SErik.Nordmark@Sun.COM 	    IPTUN_MIN_IPV6_MTU,	IPTUN_MAX_IPV4_MTU,	B_TRUE },
123*11042SErik.Nordmark@Sun.COM 	{ IPTUN_TYPE_IPV6, MAC_PLUGIN_IDENT_IPV6, IPV6_VERSION,
12410616SSebastien.Roy@Sun.COM 	    IPTUN_MIN_IPV6_MTU,	IPTUN_MAX_IPV6_MTU,	B_TRUE },
125*11042SErik.Nordmark@Sun.COM 	{ IPTUN_TYPE_6TO4, MAC_PLUGIN_IDENT_6TO4, IPV4_VERSION,
126*11042SErik.Nordmark@Sun.COM 	    IPTUN_MIN_IPV6_MTU,	IPTUN_MAX_IPV4_MTU,	B_FALSE },
127*11042SErik.Nordmark@Sun.COM 	{ IPTUN_TYPE_UNKNOWN, NULL, 0, 0, 0, B_FALSE }
12810616SSebastien.Roy@Sun.COM };
12910616SSebastien.Roy@Sun.COM 
13010616SSebastien.Roy@Sun.COM /*
13110616SSebastien.Roy@Sun.COM  * iptun_hash is an iptun_t lookup table by link ID protected by
13210616SSebastien.Roy@Sun.COM  * iptun_hash_lock.  While the hash table's integrity is maintained via
13310616SSebastien.Roy@Sun.COM  * internal locking in the mod_hash_*() functions, we need additional locking
13410616SSebastien.Roy@Sun.COM  * so that an iptun_t cannot be deleted after a hash lookup has returned an
13510616SSebastien.Roy@Sun.COM  * iptun_t and before iptun_lock has been entered.  As such, we use
13610616SSebastien.Roy@Sun.COM  * iptun_hash_lock when doing lookups and removals from iptun_hash.
13710616SSebastien.Roy@Sun.COM  */
13810616SSebastien.Roy@Sun.COM mod_hash_t	*iptun_hash;
13910616SSebastien.Roy@Sun.COM static kmutex_t	iptun_hash_lock;
14010616SSebastien.Roy@Sun.COM 
14110616SSebastien.Roy@Sun.COM static uint_t	iptun_tunnelcount;	/* total for all stacks */
14210616SSebastien.Roy@Sun.COM kmem_cache_t	*iptun_cache;
14310616SSebastien.Roy@Sun.COM ddi_taskq_t 	*iptun_taskq;
14410616SSebastien.Roy@Sun.COM 
14510616SSebastien.Roy@Sun.COM typedef enum {
14610616SSebastien.Roy@Sun.COM 	IPTUN_TASK_MTU_UPDATE,	/* tell mac about new tunnel link MTU */
14710616SSebastien.Roy@Sun.COM 	IPTUN_TASK_LADDR_UPDATE, /* tell mac about new local address */
14810616SSebastien.Roy@Sun.COM 	IPTUN_TASK_RADDR_UPDATE, /* tell mac about new remote address */
14910616SSebastien.Roy@Sun.COM 	IPTUN_TASK_LINK_UPDATE,	/* tell mac about new link state */
15010616SSebastien.Roy@Sun.COM 	IPTUN_TASK_PDATA_UPDATE	/* tell mac about updated plugin data */
15110616SSebastien.Roy@Sun.COM } iptun_task_t;
15210616SSebastien.Roy@Sun.COM 
15310616SSebastien.Roy@Sun.COM typedef struct iptun_task_data_s {
15410616SSebastien.Roy@Sun.COM 	iptun_task_t	itd_task;
15510616SSebastien.Roy@Sun.COM 	datalink_id_t	itd_linkid;
15610616SSebastien.Roy@Sun.COM } iptun_task_data_t;
15710616SSebastien.Roy@Sun.COM 
15810616SSebastien.Roy@Sun.COM static void iptun_task_dispatch(iptun_t *, iptun_task_t);
15910616SSebastien.Roy@Sun.COM static int iptun_enter(iptun_t *);
16010616SSebastien.Roy@Sun.COM static void iptun_exit(iptun_t *);
16110616SSebastien.Roy@Sun.COM static void iptun_headergen(iptun_t *, boolean_t);
16210616SSebastien.Roy@Sun.COM static void iptun_drop_pkt(mblk_t *, uint64_t *);
163*11042SErik.Nordmark@Sun.COM static void iptun_input(void *, mblk_t *, void *, ip_recv_attr_t *);
164*11042SErik.Nordmark@Sun.COM static void iptun_input_icmp(void *, mblk_t *, void *, ip_recv_attr_t *);
16510616SSebastien.Roy@Sun.COM static void iptun_output(iptun_t *, mblk_t *);
166*11042SErik.Nordmark@Sun.COM static uint32_t iptun_get_maxmtu(iptun_t *, ip_xmit_attr_t *, uint32_t);
167*11042SErik.Nordmark@Sun.COM static uint32_t iptun_update_mtu(iptun_t *, ip_xmit_attr_t *, uint32_t);
168*11042SErik.Nordmark@Sun.COM static uint32_t iptun_get_dst_pmtu(iptun_t *, ip_xmit_attr_t *);
169*11042SErik.Nordmark@Sun.COM static void iptun_update_dst_pmtu(iptun_t *, ip_xmit_attr_t *);
17010616SSebastien.Roy@Sun.COM static int iptun_setladdr(iptun_t *, const struct sockaddr_storage *);
17110616SSebastien.Roy@Sun.COM 
172*11042SErik.Nordmark@Sun.COM static void iptun_output_6to4(iptun_t *, mblk_t *);
173*11042SErik.Nordmark@Sun.COM static void iptun_output_common(iptun_t *, ip_xmit_attr_t *, mblk_t *);
174*11042SErik.Nordmark@Sun.COM static boolean_t iptun_verifyicmp(conn_t *, void *, icmph_t *, icmp6_t *,
175*11042SErik.Nordmark@Sun.COM     ip_recv_attr_t *);
176*11042SErik.Nordmark@Sun.COM 
177*11042SErik.Nordmark@Sun.COM static void iptun_notify(void *, ip_xmit_attr_t *, ixa_notify_type_t,
178*11042SErik.Nordmark@Sun.COM     ixa_notify_arg_t);
179*11042SErik.Nordmark@Sun.COM 
18010616SSebastien.Roy@Sun.COM static mac_callbacks_t iptun_m_callbacks;
18110616SSebastien.Roy@Sun.COM 
18210616SSebastien.Roy@Sun.COM static int
18310616SSebastien.Roy@Sun.COM iptun_m_getstat(void *arg, uint_t stat, uint64_t *val)
18410616SSebastien.Roy@Sun.COM {
18510616SSebastien.Roy@Sun.COM 	iptun_t	*iptun = arg;
18610616SSebastien.Roy@Sun.COM 	int	err = 0;
18710616SSebastien.Roy@Sun.COM 
18810616SSebastien.Roy@Sun.COM 	switch (stat) {
18910616SSebastien.Roy@Sun.COM 	case MAC_STAT_IERRORS:
19010616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_ierrors;
19110616SSebastien.Roy@Sun.COM 		break;
19210616SSebastien.Roy@Sun.COM 	case MAC_STAT_OERRORS:
19310616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_oerrors;
19410616SSebastien.Roy@Sun.COM 		break;
19510616SSebastien.Roy@Sun.COM 	case MAC_STAT_RBYTES:
19610616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_rbytes;
19710616SSebastien.Roy@Sun.COM 		break;
19810616SSebastien.Roy@Sun.COM 	case MAC_STAT_IPACKETS:
19910616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_ipackets;
20010616SSebastien.Roy@Sun.COM 		break;
20110616SSebastien.Roy@Sun.COM 	case MAC_STAT_OBYTES:
20210616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_obytes;
20310616SSebastien.Roy@Sun.COM 		break;
20410616SSebastien.Roy@Sun.COM 	case MAC_STAT_OPACKETS:
20510616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_opackets;
20610616SSebastien.Roy@Sun.COM 		break;
20710616SSebastien.Roy@Sun.COM 	case MAC_STAT_NORCVBUF:
20810616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_norcvbuf;
20910616SSebastien.Roy@Sun.COM 		break;
21010616SSebastien.Roy@Sun.COM 	case MAC_STAT_NOXMTBUF:
21110616SSebastien.Roy@Sun.COM 		*val = iptun->iptun_noxmtbuf;
21210616SSebastien.Roy@Sun.COM 		break;
21310616SSebastien.Roy@Sun.COM 	default:
21410616SSebastien.Roy@Sun.COM 		err = ENOTSUP;
21510616SSebastien.Roy@Sun.COM 	}
21610616SSebastien.Roy@Sun.COM 
21710616SSebastien.Roy@Sun.COM 	return (err);
21810616SSebastien.Roy@Sun.COM }
21910616SSebastien.Roy@Sun.COM 
22010616SSebastien.Roy@Sun.COM static int
22110616SSebastien.Roy@Sun.COM iptun_m_start(void *arg)
22210616SSebastien.Roy@Sun.COM {
22310616SSebastien.Roy@Sun.COM 	iptun_t	*iptun = arg;
22410616SSebastien.Roy@Sun.COM 	int	err;
22510616SSebastien.Roy@Sun.COM 
22610616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter(iptun)) == 0) {
22710616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_MAC_STARTED;
22810616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_LINK_UPDATE);
22910616SSebastien.Roy@Sun.COM 		iptun_exit(iptun);
23010616SSebastien.Roy@Sun.COM 	}
23110616SSebastien.Roy@Sun.COM 	return (err);
23210616SSebastien.Roy@Sun.COM }
23310616SSebastien.Roy@Sun.COM 
23410616SSebastien.Roy@Sun.COM static void
23510616SSebastien.Roy@Sun.COM iptun_m_stop(void *arg)
23610616SSebastien.Roy@Sun.COM {
23710616SSebastien.Roy@Sun.COM 	iptun_t *iptun = arg;
23810616SSebastien.Roy@Sun.COM 
23910616SSebastien.Roy@Sun.COM 	if (iptun_enter(iptun) == 0) {
24010616SSebastien.Roy@Sun.COM 		iptun->iptun_flags &= ~IPTUN_MAC_STARTED;
24110616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_LINK_UPDATE);
24210616SSebastien.Roy@Sun.COM 		iptun_exit(iptun);
24310616SSebastien.Roy@Sun.COM 	}
24410616SSebastien.Roy@Sun.COM }
24510616SSebastien.Roy@Sun.COM 
24610616SSebastien.Roy@Sun.COM /*
24710616SSebastien.Roy@Sun.COM  * iptun_m_setpromisc() does nothing and always succeeds.  This is because a
24810616SSebastien.Roy@Sun.COM  * tunnel data-link only ever receives packets that are destined exclusively
24910616SSebastien.Roy@Sun.COM  * for the local address of the tunnel.
25010616SSebastien.Roy@Sun.COM  */
25110616SSebastien.Roy@Sun.COM /* ARGSUSED */
25210616SSebastien.Roy@Sun.COM static int
25310616SSebastien.Roy@Sun.COM iptun_m_setpromisc(void *arg, boolean_t on)
25410616SSebastien.Roy@Sun.COM {
25510616SSebastien.Roy@Sun.COM 	return (0);
25610616SSebastien.Roy@Sun.COM }
25710616SSebastien.Roy@Sun.COM 
25810616SSebastien.Roy@Sun.COM /* ARGSUSED */
25910616SSebastien.Roy@Sun.COM static int
26010616SSebastien.Roy@Sun.COM iptun_m_multicst(void *arg, boolean_t add, const uint8_t *addrp)
26110616SSebastien.Roy@Sun.COM {
26210616SSebastien.Roy@Sun.COM 	return (ENOTSUP);
26310616SSebastien.Roy@Sun.COM }
26410616SSebastien.Roy@Sun.COM 
26510616SSebastien.Roy@Sun.COM /*
26610616SSebastien.Roy@Sun.COM  * iptun_m_unicst() sets the local address.
26710616SSebastien.Roy@Sun.COM  */
26810616SSebastien.Roy@Sun.COM /* ARGSUSED */
26910616SSebastien.Roy@Sun.COM static int
27010616SSebastien.Roy@Sun.COM iptun_m_unicst(void *arg, const uint8_t *addrp)
27110616SSebastien.Roy@Sun.COM {
27210616SSebastien.Roy@Sun.COM 	iptun_t			*iptun = arg;
27310616SSebastien.Roy@Sun.COM 	int			err;
27410616SSebastien.Roy@Sun.COM 	struct sockaddr_storage	ss;
27510616SSebastien.Roy@Sun.COM 	struct sockaddr_in	*sin;
27610616SSebastien.Roy@Sun.COM 	struct sockaddr_in6	*sin6;
27710616SSebastien.Roy@Sun.COM 
27810616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter(iptun)) == 0) {
27910616SSebastien.Roy@Sun.COM 		switch (iptun->iptun_typeinfo->iti_ipvers) {
28010616SSebastien.Roy@Sun.COM 		case IPV4_VERSION:
28110616SSebastien.Roy@Sun.COM 			sin = (struct sockaddr_in *)&ss;
28210616SSebastien.Roy@Sun.COM 			sin->sin_family = AF_INET;
28310616SSebastien.Roy@Sun.COM 			bcopy(addrp, &sin->sin_addr, sizeof (in_addr_t));
28410616SSebastien.Roy@Sun.COM 			break;
28510616SSebastien.Roy@Sun.COM 		case IPV6_VERSION:
28610616SSebastien.Roy@Sun.COM 			sin6 = (struct sockaddr_in6 *)&ss;
28710616SSebastien.Roy@Sun.COM 			sin6->sin6_family = AF_INET6;
28810616SSebastien.Roy@Sun.COM 			bcopy(addrp, &sin6->sin6_addr, sizeof (in6_addr_t));
28910616SSebastien.Roy@Sun.COM 			break;
29010616SSebastien.Roy@Sun.COM 		default:
29110616SSebastien.Roy@Sun.COM 			ASSERT(0);
29210616SSebastien.Roy@Sun.COM 		}
29310616SSebastien.Roy@Sun.COM 		err = iptun_setladdr(iptun, &ss);
29410616SSebastien.Roy@Sun.COM 		iptun_exit(iptun);
29510616SSebastien.Roy@Sun.COM 	}
29610616SSebastien.Roy@Sun.COM 	return (err);
29710616SSebastien.Roy@Sun.COM }
29810616SSebastien.Roy@Sun.COM 
29910616SSebastien.Roy@Sun.COM static mblk_t *
30010616SSebastien.Roy@Sun.COM iptun_m_tx(void *arg, mblk_t *mpchain)
30110616SSebastien.Roy@Sun.COM {
30210616SSebastien.Roy@Sun.COM 	mblk_t	*mp, *nmp;
30310616SSebastien.Roy@Sun.COM 	iptun_t	*iptun = arg;
30410616SSebastien.Roy@Sun.COM 
30510616SSebastien.Roy@Sun.COM 	if (!IS_IPTUN_RUNNING(iptun)) {
30610616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(mpchain, &iptun->iptun_noxmtbuf);
30710616SSebastien.Roy@Sun.COM 		return (NULL);
30810616SSebastien.Roy@Sun.COM 	}
30910616SSebastien.Roy@Sun.COM 
31010616SSebastien.Roy@Sun.COM 	for (mp = mpchain; mp != NULL; mp = nmp) {
31110616SSebastien.Roy@Sun.COM 		nmp = mp->b_next;
31210616SSebastien.Roy@Sun.COM 		mp->b_next = NULL;
31310616SSebastien.Roy@Sun.COM 		iptun_output(iptun, mp);
31410616SSebastien.Roy@Sun.COM 	}
31510616SSebastien.Roy@Sun.COM 
31610616SSebastien.Roy@Sun.COM 	return (NULL);
31710616SSebastien.Roy@Sun.COM }
31810616SSebastien.Roy@Sun.COM 
31910616SSebastien.Roy@Sun.COM /* ARGSUSED */
32010616SSebastien.Roy@Sun.COM static int
32110616SSebastien.Roy@Sun.COM iptun_m_setprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
32210616SSebastien.Roy@Sun.COM     uint_t pr_valsize, const void *pr_val)
32310616SSebastien.Roy@Sun.COM {
32410616SSebastien.Roy@Sun.COM 	iptun_t		*iptun = barg;
32510616SSebastien.Roy@Sun.COM 	uint32_t	value = *(uint32_t *)pr_val;
32610616SSebastien.Roy@Sun.COM 	int		err;
32710616SSebastien.Roy@Sun.COM 
32810616SSebastien.Roy@Sun.COM 	/*
32910616SSebastien.Roy@Sun.COM 	 * We need to enter this iptun_t since we'll be modifying the outer
33010616SSebastien.Roy@Sun.COM 	 * header.
33110616SSebastien.Roy@Sun.COM 	 */
33210616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter(iptun)) != 0)
33310616SSebastien.Roy@Sun.COM 		return (err);
33410616SSebastien.Roy@Sun.COM 
33510616SSebastien.Roy@Sun.COM 	switch (pr_num) {
33610616SSebastien.Roy@Sun.COM 	case MAC_PROP_IPTUN_HOPLIMIT:
33710616SSebastien.Roy@Sun.COM 		if (value < IPTUN_MIN_HOPLIMIT || value > IPTUN_MAX_HOPLIMIT) {
33810616SSebastien.Roy@Sun.COM 			err = EINVAL;
33910616SSebastien.Roy@Sun.COM 			break;
34010616SSebastien.Roy@Sun.COM 		}
34110616SSebastien.Roy@Sun.COM 		if (value != iptun->iptun_hoplimit) {
34210616SSebastien.Roy@Sun.COM 			iptun->iptun_hoplimit = (uint8_t)value;
34310616SSebastien.Roy@Sun.COM 			iptun_headergen(iptun, B_TRUE);
34410616SSebastien.Roy@Sun.COM 		}
34510616SSebastien.Roy@Sun.COM 		break;
34610616SSebastien.Roy@Sun.COM 	case MAC_PROP_IPTUN_ENCAPLIMIT:
34710616SSebastien.Roy@Sun.COM 		if (iptun->iptun_typeinfo->iti_type != IPTUN_TYPE_IPV6 ||
34810616SSebastien.Roy@Sun.COM 		    value > IPTUN_MAX_ENCAPLIMIT) {
34910616SSebastien.Roy@Sun.COM 			err = EINVAL;
35010616SSebastien.Roy@Sun.COM 			break;
35110616SSebastien.Roy@Sun.COM 		}
35210616SSebastien.Roy@Sun.COM 		if (value != iptun->iptun_encaplimit) {
35310616SSebastien.Roy@Sun.COM 			iptun->iptun_encaplimit = (uint8_t)value;
35410616SSebastien.Roy@Sun.COM 			iptun_headergen(iptun, B_TRUE);
35510616SSebastien.Roy@Sun.COM 		}
35610616SSebastien.Roy@Sun.COM 		break;
35710616SSebastien.Roy@Sun.COM 	case MAC_PROP_MTU: {
358*11042SErik.Nordmark@Sun.COM 		uint32_t maxmtu = iptun_get_maxmtu(iptun, NULL, 0);
35910616SSebastien.Roy@Sun.COM 
36010616SSebastien.Roy@Sun.COM 		if (value < iptun->iptun_typeinfo->iti_minmtu ||
36110616SSebastien.Roy@Sun.COM 		    value > maxmtu) {
36210616SSebastien.Roy@Sun.COM 			err = EINVAL;
36310616SSebastien.Roy@Sun.COM 			break;
36410616SSebastien.Roy@Sun.COM 		}
36510616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_FIXED_MTU;
36610616SSebastien.Roy@Sun.COM 		if (value != iptun->iptun_mtu) {
36710616SSebastien.Roy@Sun.COM 			iptun->iptun_mtu = value;
36810616SSebastien.Roy@Sun.COM 			iptun_task_dispatch(iptun, IPTUN_TASK_MTU_UPDATE);
36910616SSebastien.Roy@Sun.COM 		}
37010616SSebastien.Roy@Sun.COM 		break;
37110616SSebastien.Roy@Sun.COM 	}
37210616SSebastien.Roy@Sun.COM 	default:
37310616SSebastien.Roy@Sun.COM 		err = EINVAL;
37410616SSebastien.Roy@Sun.COM 	}
37510616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
37610616SSebastien.Roy@Sun.COM 	return (err);
37710616SSebastien.Roy@Sun.COM }
37810616SSebastien.Roy@Sun.COM 
37910616SSebastien.Roy@Sun.COM /* ARGSUSED */
38010616SSebastien.Roy@Sun.COM static int
38110616SSebastien.Roy@Sun.COM iptun_m_getprop(void *barg, const char *pr_name, mac_prop_id_t pr_num,
38210616SSebastien.Roy@Sun.COM     uint_t pr_flags, uint_t pr_valsize, void *pr_val, uint_t *perm)
38310616SSebastien.Roy@Sun.COM {
38410616SSebastien.Roy@Sun.COM 	iptun_t			*iptun = barg;
38510616SSebastien.Roy@Sun.COM 	mac_propval_range_t	range;
38610616SSebastien.Roy@Sun.COM 	boolean_t		is_default = (pr_flags & MAC_PROP_DEFAULT);
38710616SSebastien.Roy@Sun.COM 	boolean_t		is_possible = (pr_flags & MAC_PROP_POSSIBLE);
38810616SSebastien.Roy@Sun.COM 	int			err;
38910616SSebastien.Roy@Sun.COM 
39010616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter(iptun)) != 0)
39110616SSebastien.Roy@Sun.COM 		return (err);
39210616SSebastien.Roy@Sun.COM 
39310616SSebastien.Roy@Sun.COM 	if ((pr_flags & ~(MAC_PROP_DEFAULT | MAC_PROP_POSSIBLE)) != 0) {
39410616SSebastien.Roy@Sun.COM 		err = ENOTSUP;
39510616SSebastien.Roy@Sun.COM 		goto done;
39610616SSebastien.Roy@Sun.COM 	}
39710616SSebastien.Roy@Sun.COM 	if (is_default && is_possible) {
39810616SSebastien.Roy@Sun.COM 		err = EINVAL;
39910616SSebastien.Roy@Sun.COM 		goto done;
40010616SSebastien.Roy@Sun.COM 	}
40110616SSebastien.Roy@Sun.COM 
40210616SSebastien.Roy@Sun.COM 	*perm = MAC_PROP_PERM_RW;
40310616SSebastien.Roy@Sun.COM 
40410616SSebastien.Roy@Sun.COM 	if (is_possible) {
40510616SSebastien.Roy@Sun.COM 		if (pr_valsize < sizeof (mac_propval_range_t)) {
40610616SSebastien.Roy@Sun.COM 			err = EINVAL;
40710616SSebastien.Roy@Sun.COM 			goto done;
40810616SSebastien.Roy@Sun.COM 		}
40910616SSebastien.Roy@Sun.COM 		range.mpr_count = 1;
41010616SSebastien.Roy@Sun.COM 		range.mpr_type = MAC_PROPVAL_UINT32;
41110616SSebastien.Roy@Sun.COM 	} else if (pr_valsize < sizeof (uint32_t)) {
41210616SSebastien.Roy@Sun.COM 		err = EINVAL;
41310616SSebastien.Roy@Sun.COM 		goto done;
41410616SSebastien.Roy@Sun.COM 	}
41510616SSebastien.Roy@Sun.COM 
41610616SSebastien.Roy@Sun.COM 	switch (pr_num) {
41710616SSebastien.Roy@Sun.COM 	case MAC_PROP_IPTUN_HOPLIMIT:
41810616SSebastien.Roy@Sun.COM 		if (is_possible) {
41910616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_min = IPTUN_MIN_HOPLIMIT;
42010616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_max = IPTUN_MAX_HOPLIMIT;
42110616SSebastien.Roy@Sun.COM 		} else if (is_default) {
42210616SSebastien.Roy@Sun.COM 			*(uint32_t *)pr_val = IPTUN_DEFAULT_HOPLIMIT;
42310616SSebastien.Roy@Sun.COM 		} else {
42410616SSebastien.Roy@Sun.COM 			*(uint32_t *)pr_val = iptun->iptun_hoplimit;
42510616SSebastien.Roy@Sun.COM 		}
42610616SSebastien.Roy@Sun.COM 		break;
42710616SSebastien.Roy@Sun.COM 	case MAC_PROP_IPTUN_ENCAPLIMIT:
42810616SSebastien.Roy@Sun.COM 		if (iptun->iptun_typeinfo->iti_type != IPTUN_TYPE_IPV6) {
42910616SSebastien.Roy@Sun.COM 			err = ENOTSUP;
43010616SSebastien.Roy@Sun.COM 			goto done;
43110616SSebastien.Roy@Sun.COM 		}
43210616SSebastien.Roy@Sun.COM 		if (is_possible) {
43310616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_min = IPTUN_MIN_ENCAPLIMIT;
43410616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_max = IPTUN_MAX_ENCAPLIMIT;
43510616SSebastien.Roy@Sun.COM 		} else if (is_default) {
43610616SSebastien.Roy@Sun.COM 			*(uint32_t *)pr_val = IPTUN_DEFAULT_ENCAPLIMIT;
43710616SSebastien.Roy@Sun.COM 		} else {
43810616SSebastien.Roy@Sun.COM 			*(uint32_t *)pr_val = iptun->iptun_encaplimit;
43910616SSebastien.Roy@Sun.COM 		}
44010616SSebastien.Roy@Sun.COM 		break;
44110616SSebastien.Roy@Sun.COM 	case MAC_PROP_MTU: {
442*11042SErik.Nordmark@Sun.COM 		uint32_t maxmtu = iptun_get_maxmtu(iptun, NULL, 0);
44310616SSebastien.Roy@Sun.COM 
44410616SSebastien.Roy@Sun.COM 		if (is_possible) {
44510616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_min =
44610616SSebastien.Roy@Sun.COM 			    iptun->iptun_typeinfo->iti_minmtu;
44710616SSebastien.Roy@Sun.COM 			range.range_uint32[0].mpur_max = maxmtu;
44810616SSebastien.Roy@Sun.COM 		} else {
44910616SSebastien.Roy@Sun.COM 			/*
45010616SSebastien.Roy@Sun.COM 			 * The MAC module knows the current value and should
45110616SSebastien.Roy@Sun.COM 			 * never call us for it.  There is also no default
45210616SSebastien.Roy@Sun.COM 			 * MTU, as by default, it is a dynamic property.
45310616SSebastien.Roy@Sun.COM 			 */
45410616SSebastien.Roy@Sun.COM 			err = ENOTSUP;
45510616SSebastien.Roy@Sun.COM 			goto done;
45610616SSebastien.Roy@Sun.COM 		}
45710616SSebastien.Roy@Sun.COM 		break;
45810616SSebastien.Roy@Sun.COM 	}
45910616SSebastien.Roy@Sun.COM 	default:
46010616SSebastien.Roy@Sun.COM 		err = EINVAL;
46110616SSebastien.Roy@Sun.COM 		goto done;
46210616SSebastien.Roy@Sun.COM 	}
46310616SSebastien.Roy@Sun.COM 	if (is_possible)
46410616SSebastien.Roy@Sun.COM 		bcopy(&range, pr_val, sizeof (range));
46510616SSebastien.Roy@Sun.COM done:
46610616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
46710616SSebastien.Roy@Sun.COM 	return (err);
46810616SSebastien.Roy@Sun.COM }
46910616SSebastien.Roy@Sun.COM 
47010616SSebastien.Roy@Sun.COM uint_t
47110616SSebastien.Roy@Sun.COM iptun_count(void)
47210616SSebastien.Roy@Sun.COM {
47310616SSebastien.Roy@Sun.COM 	return (iptun_tunnelcount);
47410616SSebastien.Roy@Sun.COM }
47510616SSebastien.Roy@Sun.COM 
47610616SSebastien.Roy@Sun.COM /*
47710616SSebastien.Roy@Sun.COM  * Enter an iptun_t exclusively.  This is essentially just a mutex, but we
47810616SSebastien.Roy@Sun.COM  * don't allow iptun_enter() to succeed on a tunnel if it's in the process of
47910616SSebastien.Roy@Sun.COM  * being deleted.
48010616SSebastien.Roy@Sun.COM  */
48110616SSebastien.Roy@Sun.COM static int
48210616SSebastien.Roy@Sun.COM iptun_enter(iptun_t *iptun)
48310616SSebastien.Roy@Sun.COM {
48410616SSebastien.Roy@Sun.COM 	mutex_enter(&iptun->iptun_lock);
48510616SSebastien.Roy@Sun.COM 	while (iptun->iptun_flags & IPTUN_DELETE_PENDING)
48610616SSebastien.Roy@Sun.COM 		cv_wait(&iptun->iptun_enter_cv, &iptun->iptun_lock);
48710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_CONDEMNED) {
48810616SSebastien.Roy@Sun.COM 		mutex_exit(&iptun->iptun_lock);
48910616SSebastien.Roy@Sun.COM 		return (ENOENT);
49010616SSebastien.Roy@Sun.COM 	}
49110616SSebastien.Roy@Sun.COM 	return (0);
49210616SSebastien.Roy@Sun.COM }
49310616SSebastien.Roy@Sun.COM 
49410616SSebastien.Roy@Sun.COM /*
49510616SSebastien.Roy@Sun.COM  * Exit the tunnel entered in iptun_enter().
49610616SSebastien.Roy@Sun.COM  */
49710616SSebastien.Roy@Sun.COM static void
49810616SSebastien.Roy@Sun.COM iptun_exit(iptun_t *iptun)
49910616SSebastien.Roy@Sun.COM {
50010616SSebastien.Roy@Sun.COM 	mutex_exit(&iptun->iptun_lock);
50110616SSebastien.Roy@Sun.COM }
50210616SSebastien.Roy@Sun.COM 
50310616SSebastien.Roy@Sun.COM /*
50410616SSebastien.Roy@Sun.COM  * Enter the IP tunnel instance by datalink ID.
50510616SSebastien.Roy@Sun.COM  */
50610616SSebastien.Roy@Sun.COM static int
50710616SSebastien.Roy@Sun.COM iptun_enter_by_linkid(datalink_id_t linkid, iptun_t **iptun)
50810616SSebastien.Roy@Sun.COM {
50910616SSebastien.Roy@Sun.COM 	int err;
51010616SSebastien.Roy@Sun.COM 
51110616SSebastien.Roy@Sun.COM 	mutex_enter(&iptun_hash_lock);
51210616SSebastien.Roy@Sun.COM 	if (mod_hash_find(iptun_hash, IPTUN_HASH_KEY(linkid),
51310616SSebastien.Roy@Sun.COM 	    (mod_hash_val_t *)iptun) == 0)
51410616SSebastien.Roy@Sun.COM 		err = iptun_enter(*iptun);
51510616SSebastien.Roy@Sun.COM 	else
51610616SSebastien.Roy@Sun.COM 		err = ENOENT;
51710616SSebastien.Roy@Sun.COM 	if (err != 0)
51810616SSebastien.Roy@Sun.COM 		*iptun = NULL;
51910616SSebastien.Roy@Sun.COM 	mutex_exit(&iptun_hash_lock);
52010616SSebastien.Roy@Sun.COM 	return (err);
52110616SSebastien.Roy@Sun.COM }
52210616SSebastien.Roy@Sun.COM 
52310616SSebastien.Roy@Sun.COM /*
524*11042SErik.Nordmark@Sun.COM  * Handle tasks that were deferred through the iptun_taskq because they require
525*11042SErik.Nordmark@Sun.COM  * calling up to the mac module, and we can't call up to the mac module while
526*11042SErik.Nordmark@Sun.COM  * holding locks.
52710616SSebastien.Roy@Sun.COM  *
528*11042SErik.Nordmark@Sun.COM  * This is tricky to get right without introducing race conditions and
52910616SSebastien.Roy@Sun.COM  * deadlocks with the mac module, as we cannot issue an upcall while in the
53010616SSebastien.Roy@Sun.COM  * iptun_t.  The reason is that upcalls may try and enter the mac perimeter,
53110616SSebastien.Roy@Sun.COM  * while iptun callbacks (such as iptun_m_setprop()) called from the mac
53210616SSebastien.Roy@Sun.COM  * module will already have the perimeter held, and will then try and enter
53310616SSebastien.Roy@Sun.COM  * the iptun_t.  You can see the lock ordering problem with this; this will
53410616SSebastien.Roy@Sun.COM  * deadlock.
53510616SSebastien.Roy@Sun.COM  *
53610616SSebastien.Roy@Sun.COM  * The safe way to do this is to enter the iptun_t in question and copy the
53710616SSebastien.Roy@Sun.COM  * information we need out of it so that we can exit it and know that the
53810616SSebastien.Roy@Sun.COM  * information being passed up to the upcalls won't be subject to modification
53910616SSebastien.Roy@Sun.COM  * by other threads.  The problem now is that we need to exit it prior to
54010616SSebastien.Roy@Sun.COM  * issuing the upcall, but once we do this, a thread could come along and
54110616SSebastien.Roy@Sun.COM  * delete the iptun_t and thus the mac handle required to issue the upcall.
54210616SSebastien.Roy@Sun.COM  * To prevent this, we set the IPTUN_UPCALL_PENDING flag prior to exiting the
54310616SSebastien.Roy@Sun.COM  * iptun_t.  This flag is the condition associated with iptun_upcall_cv, which
54410616SSebastien.Roy@Sun.COM  * iptun_delete() will cv_wait() on.  When the upcall completes, we clear
54510616SSebastien.Roy@Sun.COM  * IPTUN_UPCALL_PENDING and cv_signal() any potentially waiting
54610616SSebastien.Roy@Sun.COM  * iptun_delete().  We can thus still safely use iptun->iptun_mh after having
54710616SSebastien.Roy@Sun.COM  * exited the iptun_t.
54810616SSebastien.Roy@Sun.COM  */
54910616SSebastien.Roy@Sun.COM static void
55010616SSebastien.Roy@Sun.COM iptun_task_cb(void *arg)
55110616SSebastien.Roy@Sun.COM {
55210616SSebastien.Roy@Sun.COM 	iptun_task_data_t	*itd = arg;
55310616SSebastien.Roy@Sun.COM 	iptun_task_t		task = itd->itd_task;
55410616SSebastien.Roy@Sun.COM 	datalink_id_t		linkid = itd->itd_linkid;
55510616SSebastien.Roy@Sun.COM 	iptun_t			*iptun;
55610616SSebastien.Roy@Sun.COM 	uint32_t		mtu;
55710616SSebastien.Roy@Sun.COM 	iptun_addr_t		addr;
55810616SSebastien.Roy@Sun.COM 	link_state_t		linkstate;
55910616SSebastien.Roy@Sun.COM 	size_t			header_size;
56010616SSebastien.Roy@Sun.COM 	iptun_header_t		header;
56110616SSebastien.Roy@Sun.COM 
56210616SSebastien.Roy@Sun.COM 	kmem_free(itd, sizeof (*itd));
56310616SSebastien.Roy@Sun.COM 
56410616SSebastien.Roy@Sun.COM 	/*
56510616SSebastien.Roy@Sun.COM 	 * Note that if the lookup fails, it's because the tunnel was deleted
56610616SSebastien.Roy@Sun.COM 	 * between the time the task was dispatched and now.  That isn't an
56710616SSebastien.Roy@Sun.COM 	 * error.
56810616SSebastien.Roy@Sun.COM 	 */
56910616SSebastien.Roy@Sun.COM 	if (iptun_enter_by_linkid(linkid, &iptun) != 0)
57010616SSebastien.Roy@Sun.COM 		return;
57110616SSebastien.Roy@Sun.COM 
57210616SSebastien.Roy@Sun.COM 	iptun->iptun_flags |= IPTUN_UPCALL_PENDING;
57310616SSebastien.Roy@Sun.COM 
57410616SSebastien.Roy@Sun.COM 	switch (task) {
57510616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_MTU_UPDATE:
57610616SSebastien.Roy@Sun.COM 		mtu = iptun->iptun_mtu;
57710616SSebastien.Roy@Sun.COM 		break;
57810616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_LADDR_UPDATE:
57910616SSebastien.Roy@Sun.COM 		addr = iptun->iptun_laddr;
58010616SSebastien.Roy@Sun.COM 		break;
58110616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_RADDR_UPDATE:
58210616SSebastien.Roy@Sun.COM 		addr = iptun->iptun_raddr;
58310616SSebastien.Roy@Sun.COM 		break;
58410616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_LINK_UPDATE:
58510616SSebastien.Roy@Sun.COM 		linkstate = IS_IPTUN_RUNNING(iptun) ?
58610616SSebastien.Roy@Sun.COM 		    LINK_STATE_UP : LINK_STATE_DOWN;
58710616SSebastien.Roy@Sun.COM 		break;
58810616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_PDATA_UPDATE:
58910616SSebastien.Roy@Sun.COM 		header_size = iptun->iptun_header_size;
59010616SSebastien.Roy@Sun.COM 		header = iptun->iptun_header;
59110616SSebastien.Roy@Sun.COM 		break;
59210616SSebastien.Roy@Sun.COM 	default:
59310616SSebastien.Roy@Sun.COM 		ASSERT(0);
59410616SSebastien.Roy@Sun.COM 	}
59510616SSebastien.Roy@Sun.COM 
59610616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
59710616SSebastien.Roy@Sun.COM 
59810616SSebastien.Roy@Sun.COM 	switch (task) {
59910616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_MTU_UPDATE:
60010616SSebastien.Roy@Sun.COM 		(void) mac_maxsdu_update(iptun->iptun_mh, mtu);
60110616SSebastien.Roy@Sun.COM 		break;
60210616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_LADDR_UPDATE:
60310616SSebastien.Roy@Sun.COM 		mac_unicst_update(iptun->iptun_mh, (uint8_t *)&addr.ia_addr);
60410616SSebastien.Roy@Sun.COM 		break;
60510616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_RADDR_UPDATE:
60610616SSebastien.Roy@Sun.COM 		mac_dst_update(iptun->iptun_mh, (uint8_t *)&addr.ia_addr);
60710616SSebastien.Roy@Sun.COM 		break;
60810616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_LINK_UPDATE:
60910616SSebastien.Roy@Sun.COM 		mac_link_update(iptun->iptun_mh, linkstate);
61010616SSebastien.Roy@Sun.COM 		break;
61110616SSebastien.Roy@Sun.COM 	case IPTUN_TASK_PDATA_UPDATE:
61210616SSebastien.Roy@Sun.COM 		if (mac_pdata_update(iptun->iptun_mh,
61310616SSebastien.Roy@Sun.COM 		    header_size == 0 ? NULL : &header, header_size) != 0)
61410616SSebastien.Roy@Sun.COM 			atomic_inc_64(&iptun->iptun_taskq_fail);
61510616SSebastien.Roy@Sun.COM 		break;
61610616SSebastien.Roy@Sun.COM 	}
61710616SSebastien.Roy@Sun.COM 
61810616SSebastien.Roy@Sun.COM 	mutex_enter(&iptun->iptun_lock);
61910616SSebastien.Roy@Sun.COM 	iptun->iptun_flags &= ~IPTUN_UPCALL_PENDING;
62010616SSebastien.Roy@Sun.COM 	cv_signal(&iptun->iptun_upcall_cv);
62110616SSebastien.Roy@Sun.COM 	mutex_exit(&iptun->iptun_lock);
62210616SSebastien.Roy@Sun.COM }
62310616SSebastien.Roy@Sun.COM 
62410616SSebastien.Roy@Sun.COM static void
62510616SSebastien.Roy@Sun.COM iptun_task_dispatch(iptun_t *iptun, iptun_task_t iptun_task)
62610616SSebastien.Roy@Sun.COM {
62710616SSebastien.Roy@Sun.COM 	iptun_task_data_t *itd;
62810616SSebastien.Roy@Sun.COM 
62910616SSebastien.Roy@Sun.COM 	itd = kmem_alloc(sizeof (*itd), KM_NOSLEEP);
63010616SSebastien.Roy@Sun.COM 	if (itd == NULL) {
63110616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_taskq_fail);
63210616SSebastien.Roy@Sun.COM 		return;
63310616SSebastien.Roy@Sun.COM 	}
63410616SSebastien.Roy@Sun.COM 	itd->itd_task = iptun_task;
63510616SSebastien.Roy@Sun.COM 	itd->itd_linkid = iptun->iptun_linkid;
63610616SSebastien.Roy@Sun.COM 	if (ddi_taskq_dispatch(iptun_taskq, iptun_task_cb, itd, DDI_NOSLEEP)) {
63710616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_taskq_fail);
63810616SSebastien.Roy@Sun.COM 		kmem_free(itd, sizeof (*itd));
63910616SSebastien.Roy@Sun.COM 	}
64010616SSebastien.Roy@Sun.COM }
64110616SSebastien.Roy@Sun.COM 
64210616SSebastien.Roy@Sun.COM /*
64310616SSebastien.Roy@Sun.COM  * Convert an iptun_addr_t to sockaddr_storage.
64410616SSebastien.Roy@Sun.COM  */
64510616SSebastien.Roy@Sun.COM static void
64610616SSebastien.Roy@Sun.COM iptun_getaddr(iptun_addr_t *iptun_addr, struct sockaddr_storage *ss)
64710616SSebastien.Roy@Sun.COM {
64810616SSebastien.Roy@Sun.COM 	struct sockaddr_in	*sin;
64910616SSebastien.Roy@Sun.COM 	struct sockaddr_in6	*sin6;
65010616SSebastien.Roy@Sun.COM 
65110616SSebastien.Roy@Sun.COM 	bzero(ss, sizeof (*ss));
65210616SSebastien.Roy@Sun.COM 	switch (iptun_addr->ia_family) {
65310616SSebastien.Roy@Sun.COM 	case AF_INET:
65410616SSebastien.Roy@Sun.COM 		sin = (struct sockaddr_in *)ss;
65510616SSebastien.Roy@Sun.COM 		sin->sin_addr.s_addr = iptun_addr->ia_addr.iau_addr4;
65610616SSebastien.Roy@Sun.COM 		break;
65710616SSebastien.Roy@Sun.COM 	case AF_INET6:
65810616SSebastien.Roy@Sun.COM 		sin6 = (struct sockaddr_in6 *)ss;
65910616SSebastien.Roy@Sun.COM 		sin6->sin6_addr = iptun_addr->ia_addr.iau_addr6;
66010616SSebastien.Roy@Sun.COM 		break;
66110616SSebastien.Roy@Sun.COM 	default:
66210616SSebastien.Roy@Sun.COM 		ASSERT(0);
66310616SSebastien.Roy@Sun.COM 	}
66410616SSebastien.Roy@Sun.COM 	ss->ss_family = iptun_addr->ia_family;
66510616SSebastien.Roy@Sun.COM }
66610616SSebastien.Roy@Sun.COM 
66710616SSebastien.Roy@Sun.COM /*
66810616SSebastien.Roy@Sun.COM  * General purpose function to set an IP tunnel source or destination address.
66910616SSebastien.Roy@Sun.COM  */
67010616SSebastien.Roy@Sun.COM static int
67110616SSebastien.Roy@Sun.COM iptun_setaddr(iptun_type_t iptun_type, iptun_addr_t *iptun_addr,
67210616SSebastien.Roy@Sun.COM     const struct sockaddr_storage *ss)
67310616SSebastien.Roy@Sun.COM {
67410616SSebastien.Roy@Sun.COM 	if (!IPTUN_ADDR_MATCH(iptun_type, ss->ss_family))
67510616SSebastien.Roy@Sun.COM 		return (EINVAL);
67610616SSebastien.Roy@Sun.COM 
67710616SSebastien.Roy@Sun.COM 	switch (ss->ss_family) {
67810616SSebastien.Roy@Sun.COM 	case AF_INET: {
67910616SSebastien.Roy@Sun.COM 		struct sockaddr_in *sin = (struct sockaddr_in *)ss;
68010616SSebastien.Roy@Sun.COM 
68110616SSebastien.Roy@Sun.COM 		if ((sin->sin_addr.s_addr == INADDR_ANY) ||
68210616SSebastien.Roy@Sun.COM 		    (sin->sin_addr.s_addr == INADDR_BROADCAST) ||
68310616SSebastien.Roy@Sun.COM 		    CLASSD(sin->sin_addr.s_addr)) {
68410616SSebastien.Roy@Sun.COM 			return (EADDRNOTAVAIL);
68510616SSebastien.Roy@Sun.COM 		}
68610616SSebastien.Roy@Sun.COM 		iptun_addr->ia_addr.iau_addr4 = sin->sin_addr.s_addr;
68710616SSebastien.Roy@Sun.COM 		break;
68810616SSebastien.Roy@Sun.COM 	}
68910616SSebastien.Roy@Sun.COM 	case AF_INET6: {
69010616SSebastien.Roy@Sun.COM 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss;
69110616SSebastien.Roy@Sun.COM 
69210616SSebastien.Roy@Sun.COM 		if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) ||
69310616SSebastien.Roy@Sun.COM 		    IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) ||
69410616SSebastien.Roy@Sun.COM 		    IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
69510616SSebastien.Roy@Sun.COM 			return (EADDRNOTAVAIL);
69610616SSebastien.Roy@Sun.COM 		}
69710616SSebastien.Roy@Sun.COM 		iptun_addr->ia_addr.iau_addr6 = sin6->sin6_addr;
69810616SSebastien.Roy@Sun.COM 		break;
69910616SSebastien.Roy@Sun.COM 	}
70010616SSebastien.Roy@Sun.COM 	default:
70110616SSebastien.Roy@Sun.COM 		return (EAFNOSUPPORT);
70210616SSebastien.Roy@Sun.COM 	}
70310616SSebastien.Roy@Sun.COM 	iptun_addr->ia_family = ss->ss_family;
70410616SSebastien.Roy@Sun.COM 	return (0);
70510616SSebastien.Roy@Sun.COM }
70610616SSebastien.Roy@Sun.COM 
70710616SSebastien.Roy@Sun.COM static int
70810616SSebastien.Roy@Sun.COM iptun_setladdr(iptun_t *iptun, const struct sockaddr_storage *laddr)
70910616SSebastien.Roy@Sun.COM {
71010616SSebastien.Roy@Sun.COM 	return (iptun_setaddr(iptun->iptun_typeinfo->iti_type,
71110616SSebastien.Roy@Sun.COM 	    &iptun->iptun_laddr, laddr));
71210616SSebastien.Roy@Sun.COM }
71310616SSebastien.Roy@Sun.COM 
71410616SSebastien.Roy@Sun.COM static int
71510616SSebastien.Roy@Sun.COM iptun_setraddr(iptun_t *iptun, const struct sockaddr_storage *raddr)
71610616SSebastien.Roy@Sun.COM {
71710616SSebastien.Roy@Sun.COM 	if (!(iptun->iptun_typeinfo->iti_hasraddr))
71810616SSebastien.Roy@Sun.COM 		return (EINVAL);
71910616SSebastien.Roy@Sun.COM 	return (iptun_setaddr(iptun->iptun_typeinfo->iti_type,
72010616SSebastien.Roy@Sun.COM 	    &iptun->iptun_raddr, raddr));
72110616SSebastien.Roy@Sun.COM }
72210616SSebastien.Roy@Sun.COM 
72310616SSebastien.Roy@Sun.COM static boolean_t
72410616SSebastien.Roy@Sun.COM iptun_canbind(iptun_t *iptun)
72510616SSebastien.Roy@Sun.COM {
72610616SSebastien.Roy@Sun.COM 	/*
72710616SSebastien.Roy@Sun.COM 	 * A tunnel may bind when its source address has been set, and if its
72810616SSebastien.Roy@Sun.COM 	 * tunnel type requires one, also its destination address.
72910616SSebastien.Roy@Sun.COM 	 */
73010616SSebastien.Roy@Sun.COM 	return ((iptun->iptun_flags & IPTUN_LADDR) &&
73110616SSebastien.Roy@Sun.COM 	    ((iptun->iptun_flags & IPTUN_RADDR) ||
73210616SSebastien.Roy@Sun.COM 	    !(iptun->iptun_typeinfo->iti_hasraddr)));
73310616SSebastien.Roy@Sun.COM }
73410616SSebastien.Roy@Sun.COM 
735*11042SErik.Nordmark@Sun.COM /*
736*11042SErik.Nordmark@Sun.COM  * Verify that the local address is valid, and insert in the fanout
737*11042SErik.Nordmark@Sun.COM  */
73810616SSebastien.Roy@Sun.COM static int
73910616SSebastien.Roy@Sun.COM iptun_bind(iptun_t *iptun)
74010616SSebastien.Roy@Sun.COM {
741*11042SErik.Nordmark@Sun.COM 	conn_t			*connp = iptun->iptun_connp;
742*11042SErik.Nordmark@Sun.COM 	int			error = 0;
743*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t		*ixa;
744*11042SErik.Nordmark@Sun.COM 	iulp_t			uinfo;
745*11042SErik.Nordmark@Sun.COM 	ip_stack_t		*ipst = connp->conn_netstack->netstack_ip;
746*11042SErik.Nordmark@Sun.COM 
747*11042SErik.Nordmark@Sun.COM 	/* Get an exclusive ixa for this thread, and replace conn_ixa */
748*11042SErik.Nordmark@Sun.COM 	ixa = conn_get_ixa(connp, B_TRUE);
749*11042SErik.Nordmark@Sun.COM 	if (ixa == NULL)
750*11042SErik.Nordmark@Sun.COM 		return (ENOMEM);
751*11042SErik.Nordmark@Sun.COM 	ASSERT(ixa->ixa_refcnt >= 2);
752*11042SErik.Nordmark@Sun.COM 	ASSERT(ixa == connp->conn_ixa);
753*11042SErik.Nordmark@Sun.COM 
754*11042SErik.Nordmark@Sun.COM 	/* We create PMTU state including for 6to4 */
755*11042SErik.Nordmark@Sun.COM 	ixa->ixa_flags |= IXAF_PMTU_DISCOVERY;
75610616SSebastien.Roy@Sun.COM 
75710616SSebastien.Roy@Sun.COM 	ASSERT(iptun_canbind(iptun));
75810616SSebastien.Roy@Sun.COM 
759*11042SErik.Nordmark@Sun.COM 	mutex_enter(&connp->conn_lock);
760*11042SErik.Nordmark@Sun.COM 	/*
761*11042SErik.Nordmark@Sun.COM 	 * Note that conn_proto can't be set since the upper protocol
762*11042SErik.Nordmark@Sun.COM 	 * can be both 41 and 4 when IPv6 and IPv4 are over the same tunnel.
763*11042SErik.Nordmark@Sun.COM 	 * ipcl_iptun_classify doesn't use conn_proto.
764*11042SErik.Nordmark@Sun.COM 	 */
765*11042SErik.Nordmark@Sun.COM 	connp->conn_ipversion = iptun->iptun_typeinfo->iti_ipvers;
766*11042SErik.Nordmark@Sun.COM 
76710616SSebastien.Roy@Sun.COM 	switch (iptun->iptun_typeinfo->iti_type) {
76810616SSebastien.Roy@Sun.COM 	case IPTUN_TYPE_IPV4:
769*11042SErik.Nordmark@Sun.COM 		IN6_IPADDR_TO_V4MAPPED(iptun->iptun_laddr4,
770*11042SErik.Nordmark@Sun.COM 		    &connp->conn_laddr_v6);
771*11042SErik.Nordmark@Sun.COM 		IN6_IPADDR_TO_V4MAPPED(iptun->iptun_raddr4,
772*11042SErik.Nordmark@Sun.COM 		    &connp->conn_faddr_v6);
773*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags |= IXAF_IS_IPV4;
774*11042SErik.Nordmark@Sun.COM 		if (ip_laddr_verify_v4(iptun->iptun_laddr4, IPCL_ZONEID(connp),
775*11042SErik.Nordmark@Sun.COM 		    ipst, B_FALSE) != IPVL_UNICAST_UP) {
776*11042SErik.Nordmark@Sun.COM 			mutex_exit(&connp->conn_lock);
777*11042SErik.Nordmark@Sun.COM 			error = EADDRNOTAVAIL;
778*11042SErik.Nordmark@Sun.COM 			goto done;
779*11042SErik.Nordmark@Sun.COM 		}
78010616SSebastien.Roy@Sun.COM 		break;
78110616SSebastien.Roy@Sun.COM 	case IPTUN_TYPE_IPV6:
782*11042SErik.Nordmark@Sun.COM 		connp->conn_laddr_v6 = iptun->iptun_laddr6;
783*11042SErik.Nordmark@Sun.COM 		connp->conn_faddr_v6 = iptun->iptun_raddr6;
784*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags &= ~IXAF_IS_IPV4;
785*11042SErik.Nordmark@Sun.COM 		/* We use a zero scopeid for now */
786*11042SErik.Nordmark@Sun.COM 		if (ip_laddr_verify_v6(&iptun->iptun_laddr6, IPCL_ZONEID(connp),
787*11042SErik.Nordmark@Sun.COM 		    ipst, B_FALSE, 0) != IPVL_UNICAST_UP) {
788*11042SErik.Nordmark@Sun.COM 			mutex_exit(&connp->conn_lock);
789*11042SErik.Nordmark@Sun.COM 			error = EADDRNOTAVAIL;
790*11042SErik.Nordmark@Sun.COM 			goto done;
791*11042SErik.Nordmark@Sun.COM 		}
79210616SSebastien.Roy@Sun.COM 		break;
79310616SSebastien.Roy@Sun.COM 	case IPTUN_TYPE_6TO4:
794*11042SErik.Nordmark@Sun.COM 		IN6_IPADDR_TO_V4MAPPED(iptun->iptun_laddr4,
795*11042SErik.Nordmark@Sun.COM 		    &connp->conn_laddr_v6);
796*11042SErik.Nordmark@Sun.COM 		IN6_IPADDR_TO_V4MAPPED(INADDR_ANY, &connp->conn_faddr_v6);
797*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags |= IXAF_IS_IPV4;
798*11042SErik.Nordmark@Sun.COM 		mutex_exit(&connp->conn_lock);
799*11042SErik.Nordmark@Sun.COM 
800*11042SErik.Nordmark@Sun.COM 		switch (ip_laddr_verify_v4(iptun->iptun_laddr4,
801*11042SErik.Nordmark@Sun.COM 		    IPCL_ZONEID(connp), ipst, B_FALSE)) {
802*11042SErik.Nordmark@Sun.COM 		case IPVL_UNICAST_UP:
803*11042SErik.Nordmark@Sun.COM 		case IPVL_UNICAST_DOWN:
804*11042SErik.Nordmark@Sun.COM 			break;
805*11042SErik.Nordmark@Sun.COM 		default:
806*11042SErik.Nordmark@Sun.COM 			error = EADDRNOTAVAIL;
807*11042SErik.Nordmark@Sun.COM 			goto done;
808*11042SErik.Nordmark@Sun.COM 		}
809*11042SErik.Nordmark@Sun.COM 		goto insert;
81010616SSebastien.Roy@Sun.COM 	}
81110616SSebastien.Roy@Sun.COM 
812*11042SErik.Nordmark@Sun.COM 	/* In case previous destination was multirt */
813*11042SErik.Nordmark@Sun.COM 	ip_attr_newdst(ixa);
814*11042SErik.Nordmark@Sun.COM 
815*11042SErik.Nordmark@Sun.COM 	/*
816*11042SErik.Nordmark@Sun.COM 	 * When we set a tunnel's destination address, we do not
817*11042SErik.Nordmark@Sun.COM 	 * care if the destination is reachable.  Transient routing
818*11042SErik.Nordmark@Sun.COM 	 * issues should not inhibit the creation of a tunnel
819*11042SErik.Nordmark@Sun.COM 	 * interface, for example. Thus we pass B_FALSE here.
820*11042SErik.Nordmark@Sun.COM 	 */
821*11042SErik.Nordmark@Sun.COM 	connp->conn_saddr_v6 = connp->conn_laddr_v6;
822*11042SErik.Nordmark@Sun.COM 	mutex_exit(&connp->conn_lock);
823*11042SErik.Nordmark@Sun.COM 
824*11042SErik.Nordmark@Sun.COM 	/* As long as the MTU is large we avoid fragmentation */
825*11042SErik.Nordmark@Sun.COM 	ixa->ixa_flags |= IXAF_DONTFRAG | IXAF_PMTU_IPV4_DF;
826*11042SErik.Nordmark@Sun.COM 
827*11042SErik.Nordmark@Sun.COM 	/* We handle IPsec in iptun_output_common */
828*11042SErik.Nordmark@Sun.COM 	error = ip_attr_connect(connp, ixa, &connp->conn_saddr_v6,
829*11042SErik.Nordmark@Sun.COM 	    &connp->conn_faddr_v6, &connp->conn_faddr_v6, 0,
830*11042SErik.Nordmark@Sun.COM 	    &connp->conn_saddr_v6, &uinfo, 0);
831*11042SErik.Nordmark@Sun.COM 
832*11042SErik.Nordmark@Sun.COM 	if (error != 0)
833*11042SErik.Nordmark@Sun.COM 		goto done;
834*11042SErik.Nordmark@Sun.COM 
835*11042SErik.Nordmark@Sun.COM 	/* saddr shouldn't change since it was already set */
836*11042SErik.Nordmark@Sun.COM 	ASSERT(IN6_ARE_ADDR_EQUAL(&connp->conn_laddr_v6,
837*11042SErik.Nordmark@Sun.COM 	    &connp->conn_saddr_v6));
838*11042SErik.Nordmark@Sun.COM 
839*11042SErik.Nordmark@Sun.COM 	/* We set IXAF_VERIFY_PMTU to catch PMTU increases */
840*11042SErik.Nordmark@Sun.COM 	ixa->ixa_flags |= IXAF_VERIFY_PMTU;
841*11042SErik.Nordmark@Sun.COM 	ASSERT(uinfo.iulp_mtu != 0);
842*11042SErik.Nordmark@Sun.COM 
843*11042SErik.Nordmark@Sun.COM 	/*
844*11042SErik.Nordmark@Sun.COM 	 * Allow setting new policies.
845*11042SErik.Nordmark@Sun.COM 	 * The addresses/ports are already set, thus the IPsec policy calls
846*11042SErik.Nordmark@Sun.COM 	 * can handle their passed-in conn's.
847*11042SErik.Nordmark@Sun.COM 	 */
848*11042SErik.Nordmark@Sun.COM 	connp->conn_policy_cached = B_FALSE;
849*11042SErik.Nordmark@Sun.COM 
850*11042SErik.Nordmark@Sun.COM insert:
851*11042SErik.Nordmark@Sun.COM 	error = ipcl_conn_insert(connp);
852*11042SErik.Nordmark@Sun.COM 	if (error != 0)
853*11042SErik.Nordmark@Sun.COM 		goto done;
854*11042SErik.Nordmark@Sun.COM 
855*11042SErik.Nordmark@Sun.COM 	/* Record this as the "last" send even though we haven't sent any */
856*11042SErik.Nordmark@Sun.COM 	connp->conn_v6lastdst = connp->conn_faddr_v6;
857*11042SErik.Nordmark@Sun.COM 
858*11042SErik.Nordmark@Sun.COM 	iptun->iptun_flags |= IPTUN_BOUND;
859*11042SErik.Nordmark@Sun.COM 	/*
860*11042SErik.Nordmark@Sun.COM 	 * Now that we're bound with ip below us, this is a good
861*11042SErik.Nordmark@Sun.COM 	 * time to initialize the destination path MTU and to
862*11042SErik.Nordmark@Sun.COM 	 * re-calculate the tunnel's link MTU.
863*11042SErik.Nordmark@Sun.COM 	 */
864*11042SErik.Nordmark@Sun.COM 	(void) iptun_update_mtu(iptun, ixa, 0);
865*11042SErik.Nordmark@Sun.COM 
866*11042SErik.Nordmark@Sun.COM 	if (IS_IPTUN_RUNNING(iptun))
867*11042SErik.Nordmark@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_LINK_UPDATE);
868*11042SErik.Nordmark@Sun.COM 
869*11042SErik.Nordmark@Sun.COM done:
870*11042SErik.Nordmark@Sun.COM 	ixa_refrele(ixa);
871*11042SErik.Nordmark@Sun.COM 	return (error);
87210616SSebastien.Roy@Sun.COM }
87310616SSebastien.Roy@Sun.COM 
87410616SSebastien.Roy@Sun.COM static void
87510616SSebastien.Roy@Sun.COM iptun_unbind(iptun_t *iptun)
87610616SSebastien.Roy@Sun.COM {
87710616SSebastien.Roy@Sun.COM 	ASSERT(iptun->iptun_flags & IPTUN_BOUND);
87810616SSebastien.Roy@Sun.COM 	ASSERT(mutex_owned(&iptun->iptun_lock) ||
87910616SSebastien.Roy@Sun.COM 	    (iptun->iptun_flags & IPTUN_CONDEMNED));
88010616SSebastien.Roy@Sun.COM 	ip_unbind(iptun->iptun_connp);
88110616SSebastien.Roy@Sun.COM 	iptun->iptun_flags &= ~IPTUN_BOUND;
88210616SSebastien.Roy@Sun.COM 	if (!(iptun->iptun_flags & IPTUN_CONDEMNED))
88310616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_LINK_UPDATE);
88410616SSebastien.Roy@Sun.COM }
88510616SSebastien.Roy@Sun.COM 
88610616SSebastien.Roy@Sun.COM /*
88710616SSebastien.Roy@Sun.COM  * Re-generate the template data-link header for a given IP tunnel given the
88810616SSebastien.Roy@Sun.COM  * tunnel's current parameters.
88910616SSebastien.Roy@Sun.COM  */
89010616SSebastien.Roy@Sun.COM static void
89110616SSebastien.Roy@Sun.COM iptun_headergen(iptun_t *iptun, boolean_t update_mac)
89210616SSebastien.Roy@Sun.COM {
89310616SSebastien.Roy@Sun.COM 	switch (iptun->iptun_typeinfo->iti_ipvers) {
89410616SSebastien.Roy@Sun.COM 	case IPV4_VERSION:
89510616SSebastien.Roy@Sun.COM 		/*
89610616SSebastien.Roy@Sun.COM 		 * We only need to use a custom IP header if the administrator
89710616SSebastien.Roy@Sun.COM 		 * has supplied a non-default hoplimit.
89810616SSebastien.Roy@Sun.COM 		 */
89910616SSebastien.Roy@Sun.COM 		if (iptun->iptun_hoplimit == IPTUN_DEFAULT_HOPLIMIT) {
90010616SSebastien.Roy@Sun.COM 			iptun->iptun_header_size = 0;
90110616SSebastien.Roy@Sun.COM 			break;
90210616SSebastien.Roy@Sun.COM 		}
90310616SSebastien.Roy@Sun.COM 		iptun->iptun_header_size = sizeof (ipha_t);
90410616SSebastien.Roy@Sun.COM 		iptun->iptun_header4.ipha_version_and_hdr_length =
90510616SSebastien.Roy@Sun.COM 		    IP_SIMPLE_HDR_VERSION;
90610616SSebastien.Roy@Sun.COM 		iptun->iptun_header4.ipha_fragment_offset_and_flags =
90710616SSebastien.Roy@Sun.COM 		    htons(IPH_DF);
90810616SSebastien.Roy@Sun.COM 		iptun->iptun_header4.ipha_ttl = iptun->iptun_hoplimit;
90910616SSebastien.Roy@Sun.COM 		break;
91010616SSebastien.Roy@Sun.COM 	case IPV6_VERSION: {
91110616SSebastien.Roy@Sun.COM 		ip6_t	*ip6hp = &iptun->iptun_header6.it6h_ip6h;
91210616SSebastien.Roy@Sun.COM 
91310616SSebastien.Roy@Sun.COM 		/*
91410616SSebastien.Roy@Sun.COM 		 * We only need to use a custom IPv6 header if either the
91510616SSebastien.Roy@Sun.COM 		 * administrator has supplied a non-default hoplimit, or we
91610616SSebastien.Roy@Sun.COM 		 * need to include an encapsulation limit option in the outer
91710616SSebastien.Roy@Sun.COM 		 * header.
91810616SSebastien.Roy@Sun.COM 		 */
91910616SSebastien.Roy@Sun.COM 		if (iptun->iptun_hoplimit == IPTUN_DEFAULT_HOPLIMIT &&
92010616SSebastien.Roy@Sun.COM 		    iptun->iptun_encaplimit == 0) {
92110616SSebastien.Roy@Sun.COM 			iptun->iptun_header_size = 0;
92210616SSebastien.Roy@Sun.COM 			break;
92310616SSebastien.Roy@Sun.COM 		}
92410616SSebastien.Roy@Sun.COM 
92510616SSebastien.Roy@Sun.COM 		(void) memset(ip6hp, 0, sizeof (*ip6hp));
92610616SSebastien.Roy@Sun.COM 		if (iptun->iptun_encaplimit == 0) {
92710616SSebastien.Roy@Sun.COM 			iptun->iptun_header_size = sizeof (ip6_t);
92810616SSebastien.Roy@Sun.COM 			ip6hp->ip6_nxt = IPPROTO_NONE;
92910616SSebastien.Roy@Sun.COM 		} else {
93010616SSebastien.Roy@Sun.COM 			iptun_encaplim_t	*iel;
93110616SSebastien.Roy@Sun.COM 
93210616SSebastien.Roy@Sun.COM 			iptun->iptun_header_size = sizeof (iptun_ipv6hdrs_t);
93310616SSebastien.Roy@Sun.COM 			/*
93410616SSebastien.Roy@Sun.COM 			 * The mac_ipv6 plugin requires ip6_plen to be in host
93510616SSebastien.Roy@Sun.COM 			 * byte order and reflect the extension headers
93610616SSebastien.Roy@Sun.COM 			 * present in the template.  The actual network byte
93710616SSebastien.Roy@Sun.COM 			 * order ip6_plen will be set on a per-packet basis on
93810616SSebastien.Roy@Sun.COM 			 * transmit.
93910616SSebastien.Roy@Sun.COM 			 */
94010616SSebastien.Roy@Sun.COM 			ip6hp->ip6_plen = sizeof (*iel);
94110616SSebastien.Roy@Sun.COM 			ip6hp->ip6_nxt = IPPROTO_DSTOPTS;
94210616SSebastien.Roy@Sun.COM 			iel = &iptun->iptun_header6.it6h_encaplim;
94310616SSebastien.Roy@Sun.COM 			*iel = iptun_encaplim_init;
94410616SSebastien.Roy@Sun.COM 			iel->iel_telopt.ip6ot_encap_limit =
94510616SSebastien.Roy@Sun.COM 			    iptun->iptun_encaplimit;
94610616SSebastien.Roy@Sun.COM 		}
94710616SSebastien.Roy@Sun.COM 
94810616SSebastien.Roy@Sun.COM 		ip6hp->ip6_hlim = iptun->iptun_hoplimit;
94910616SSebastien.Roy@Sun.COM 		break;
95010616SSebastien.Roy@Sun.COM 	}
95110616SSebastien.Roy@Sun.COM 	}
95210616SSebastien.Roy@Sun.COM 
95310616SSebastien.Roy@Sun.COM 	if (update_mac)
95410616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_PDATA_UPDATE);
95510616SSebastien.Roy@Sun.COM }
95610616SSebastien.Roy@Sun.COM 
95710616SSebastien.Roy@Sun.COM /*
95810616SSebastien.Roy@Sun.COM  * Insert inbound and outbound IPv4 and IPv6 policy into the given policy
95910616SSebastien.Roy@Sun.COM  * head.
96010616SSebastien.Roy@Sun.COM  */
96110616SSebastien.Roy@Sun.COM static boolean_t
96210616SSebastien.Roy@Sun.COM iptun_insert_simple_policies(ipsec_policy_head_t *ph, ipsec_act_t *actp,
96310616SSebastien.Roy@Sun.COM     uint_t n, netstack_t *ns)
96410616SSebastien.Roy@Sun.COM {
96510616SSebastien.Roy@Sun.COM 	int f = IPSEC_AF_V4;
96610616SSebastien.Roy@Sun.COM 
96710616SSebastien.Roy@Sun.COM 	if (!ipsec_polhead_insert(ph, actp, n, f, IPSEC_TYPE_INBOUND, ns) ||
96810616SSebastien.Roy@Sun.COM 	    !ipsec_polhead_insert(ph, actp, n, f, IPSEC_TYPE_OUTBOUND, ns))
96910616SSebastien.Roy@Sun.COM 		return (B_FALSE);
97010616SSebastien.Roy@Sun.COM 
97110616SSebastien.Roy@Sun.COM 	f = IPSEC_AF_V6;
97210616SSebastien.Roy@Sun.COM 	return (ipsec_polhead_insert(ph, actp, n, f, IPSEC_TYPE_INBOUND, ns) &&
97310616SSebastien.Roy@Sun.COM 	    ipsec_polhead_insert(ph, actp, n, f, IPSEC_TYPE_OUTBOUND, ns));
97410616SSebastien.Roy@Sun.COM }
97510616SSebastien.Roy@Sun.COM 
97610616SSebastien.Roy@Sun.COM /*
97710616SSebastien.Roy@Sun.COM  * Used to set IPsec policy when policy is set through the IPTUN_CREATE or
97810616SSebastien.Roy@Sun.COM  * IPTUN_MODIFY ioctls.
97910616SSebastien.Roy@Sun.COM  */
98010616SSebastien.Roy@Sun.COM static int
98110616SSebastien.Roy@Sun.COM iptun_set_sec_simple(iptun_t *iptun, const ipsec_req_t *ipsr)
98210616SSebastien.Roy@Sun.COM {
98310616SSebastien.Roy@Sun.COM 	int		rc = 0;
98410616SSebastien.Roy@Sun.COM 	uint_t		nact;
98510616SSebastien.Roy@Sun.COM 	ipsec_act_t	*actp = NULL;
98610616SSebastien.Roy@Sun.COM 	boolean_t	clear_all, old_policy = B_FALSE;
98710616SSebastien.Roy@Sun.COM 	ipsec_tun_pol_t	*itp;
98810616SSebastien.Roy@Sun.COM 	char		name[MAXLINKNAMELEN];
98910616SSebastien.Roy@Sun.COM 	uint64_t	gen;
99010616SSebastien.Roy@Sun.COM 	netstack_t	*ns = iptun->iptun_ns;
99110616SSebastien.Roy@Sun.COM 
99210616SSebastien.Roy@Sun.COM 	/* Can't specify self-encap on a tunnel. */
99310616SSebastien.Roy@Sun.COM 	if (ipsr->ipsr_self_encap_req != 0)
99410616SSebastien.Roy@Sun.COM 		return (EINVAL);
99510616SSebastien.Roy@Sun.COM 
99610616SSebastien.Roy@Sun.COM 	/*
99710616SSebastien.Roy@Sun.COM 	 * If it's a "clear-all" entry, unset the security flags and resume
99810616SSebastien.Roy@Sun.COM 	 * normal cleartext (or inherit-from-global) policy.
99910616SSebastien.Roy@Sun.COM 	 */
100010616SSebastien.Roy@Sun.COM 	clear_all = ((ipsr->ipsr_ah_req & IPTUN_IPSEC_REQ_MASK) == 0 &&
100110616SSebastien.Roy@Sun.COM 	    (ipsr->ipsr_esp_req & IPTUN_IPSEC_REQ_MASK) == 0);
100210616SSebastien.Roy@Sun.COM 
100310616SSebastien.Roy@Sun.COM 	ASSERT(mutex_owned(&iptun->iptun_lock));
100410616SSebastien.Roy@Sun.COM 	itp = iptun->iptun_itp;
100510616SSebastien.Roy@Sun.COM 	if (itp == NULL) {
100610616SSebastien.Roy@Sun.COM 		if (clear_all)
100710616SSebastien.Roy@Sun.COM 			goto bail;
100810616SSebastien.Roy@Sun.COM 		if ((rc = dls_mgmt_get_linkinfo(iptun->iptun_linkid, name, NULL,
100910616SSebastien.Roy@Sun.COM 		    NULL, NULL)) != 0)
101010616SSebastien.Roy@Sun.COM 			goto bail;
101110616SSebastien.Roy@Sun.COM 		ASSERT(name[0] != '\0');
101210616SSebastien.Roy@Sun.COM 		if ((itp = create_tunnel_policy(name, &rc, &gen, ns)) == NULL)
101310616SSebastien.Roy@Sun.COM 			goto bail;
101410616SSebastien.Roy@Sun.COM 		iptun->iptun_itp = itp;
101510616SSebastien.Roy@Sun.COM 	}
101610616SSebastien.Roy@Sun.COM 
101710616SSebastien.Roy@Sun.COM 	/* Allocate the actvec now, before holding itp or polhead locks. */
101810616SSebastien.Roy@Sun.COM 	ipsec_actvec_from_req(ipsr, &actp, &nact, ns);
101910616SSebastien.Roy@Sun.COM 	if (actp == NULL) {
102010616SSebastien.Roy@Sun.COM 		rc = ENOMEM;
102110616SSebastien.Roy@Sun.COM 		goto bail;
102210616SSebastien.Roy@Sun.COM 	}
102310616SSebastien.Roy@Sun.COM 
102410616SSebastien.Roy@Sun.COM 	/*
102510616SSebastien.Roy@Sun.COM 	 * Just write on the active polhead.  Save the primary/secondary stuff
102610616SSebastien.Roy@Sun.COM 	 * for spdsock operations.
102710616SSebastien.Roy@Sun.COM 	 *
102810616SSebastien.Roy@Sun.COM 	 * Mutex because we need to write to the polhead AND flags atomically.
102910616SSebastien.Roy@Sun.COM 	 * Other threads will acquire the polhead lock as a reader if the
103010616SSebastien.Roy@Sun.COM 	 * (unprotected) flag is set.
103110616SSebastien.Roy@Sun.COM 	 */
103210616SSebastien.Roy@Sun.COM 	mutex_enter(&itp->itp_lock);
103310616SSebastien.Roy@Sun.COM 	if (itp->itp_flags & ITPF_P_TUNNEL) {
103410616SSebastien.Roy@Sun.COM 		/* Oops, we lost a race.  Let's get out of here. */
103510616SSebastien.Roy@Sun.COM 		rc = EBUSY;
103610616SSebastien.Roy@Sun.COM 		goto mutex_bail;
103710616SSebastien.Roy@Sun.COM 	}
103810616SSebastien.Roy@Sun.COM 	old_policy = ((itp->itp_flags & ITPF_P_ACTIVE) != 0);
103910616SSebastien.Roy@Sun.COM 
104010616SSebastien.Roy@Sun.COM 	if (old_policy) {
104110616SSebastien.Roy@Sun.COM 		ITPF_CLONE(itp->itp_flags);
104210616SSebastien.Roy@Sun.COM 		rc = ipsec_copy_polhead(itp->itp_policy, itp->itp_inactive, ns);
104310616SSebastien.Roy@Sun.COM 		if (rc != 0) {
104410616SSebastien.Roy@Sun.COM 			/* inactive has already been cleared. */
104510616SSebastien.Roy@Sun.COM 			itp->itp_flags &= ~ITPF_IFLAGS;
104610616SSebastien.Roy@Sun.COM 			goto mutex_bail;
104710616SSebastien.Roy@Sun.COM 		}
104810616SSebastien.Roy@Sun.COM 		rw_enter(&itp->itp_policy->iph_lock, RW_WRITER);
104910616SSebastien.Roy@Sun.COM 		ipsec_polhead_flush(itp->itp_policy, ns);
105010616SSebastien.Roy@Sun.COM 	} else {
105110616SSebastien.Roy@Sun.COM 		/* Else assume itp->itp_policy is already flushed. */
105210616SSebastien.Roy@Sun.COM 		rw_enter(&itp->itp_policy->iph_lock, RW_WRITER);
105310616SSebastien.Roy@Sun.COM 	}
105410616SSebastien.Roy@Sun.COM 
105510616SSebastien.Roy@Sun.COM 	if (clear_all) {
105610616SSebastien.Roy@Sun.COM 		ASSERT(avl_numnodes(&itp->itp_policy->iph_rulebyid) == 0);
105710616SSebastien.Roy@Sun.COM 		itp->itp_flags &= ~ITPF_PFLAGS;
105810616SSebastien.Roy@Sun.COM 		rw_exit(&itp->itp_policy->iph_lock);
105910616SSebastien.Roy@Sun.COM 		old_policy = B_FALSE;	/* Clear out the inactive one too. */
106010616SSebastien.Roy@Sun.COM 		goto recover_bail;
106110616SSebastien.Roy@Sun.COM 	}
106210616SSebastien.Roy@Sun.COM 
106310616SSebastien.Roy@Sun.COM 	if (iptun_insert_simple_policies(itp->itp_policy, actp, nact, ns)) {
106410616SSebastien.Roy@Sun.COM 		rw_exit(&itp->itp_policy->iph_lock);
106510616SSebastien.Roy@Sun.COM 		/*
106610616SSebastien.Roy@Sun.COM 		 * Adjust MTU and make sure the DL side knows what's up.
106710616SSebastien.Roy@Sun.COM 		 */
106810616SSebastien.Roy@Sun.COM 		itp->itp_flags = ITPF_P_ACTIVE;
1069*11042SErik.Nordmark@Sun.COM 		(void) iptun_update_mtu(iptun, NULL, 0);
107010616SSebastien.Roy@Sun.COM 		old_policy = B_FALSE;	/* Blank out inactive - we succeeded */
107110616SSebastien.Roy@Sun.COM 	} else {
107210616SSebastien.Roy@Sun.COM 		rw_exit(&itp->itp_policy->iph_lock);
107310616SSebastien.Roy@Sun.COM 		rc = ENOMEM;
107410616SSebastien.Roy@Sun.COM 	}
107510616SSebastien.Roy@Sun.COM 
107610616SSebastien.Roy@Sun.COM recover_bail:
107710616SSebastien.Roy@Sun.COM 	if (old_policy) {
107810616SSebastien.Roy@Sun.COM 		/* Recover policy in in active polhead. */
107910616SSebastien.Roy@Sun.COM 		ipsec_swap_policy(itp->itp_policy, itp->itp_inactive, ns);
108010616SSebastien.Roy@Sun.COM 		ITPF_SWAP(itp->itp_flags);
108110616SSebastien.Roy@Sun.COM 	}
108210616SSebastien.Roy@Sun.COM 
108310616SSebastien.Roy@Sun.COM 	/* Clear policy in inactive polhead. */
108410616SSebastien.Roy@Sun.COM 	itp->itp_flags &= ~ITPF_IFLAGS;
108510616SSebastien.Roy@Sun.COM 	rw_enter(&itp->itp_inactive->iph_lock, RW_WRITER);
108610616SSebastien.Roy@Sun.COM 	ipsec_polhead_flush(itp->itp_inactive, ns);
108710616SSebastien.Roy@Sun.COM 	rw_exit(&itp->itp_inactive->iph_lock);
108810616SSebastien.Roy@Sun.COM 
108910616SSebastien.Roy@Sun.COM mutex_bail:
109010616SSebastien.Roy@Sun.COM 	mutex_exit(&itp->itp_lock);
109110616SSebastien.Roy@Sun.COM 
109210616SSebastien.Roy@Sun.COM bail:
109310616SSebastien.Roy@Sun.COM 	if (actp != NULL)
109410616SSebastien.Roy@Sun.COM 		ipsec_actvec_free(actp, nact);
109510616SSebastien.Roy@Sun.COM 
109610616SSebastien.Roy@Sun.COM 	return (rc);
109710616SSebastien.Roy@Sun.COM }
109810616SSebastien.Roy@Sun.COM 
109910616SSebastien.Roy@Sun.COM static iptun_typeinfo_t *
110010616SSebastien.Roy@Sun.COM iptun_gettypeinfo(iptun_type_t type)
110110616SSebastien.Roy@Sun.COM {
110210616SSebastien.Roy@Sun.COM 	int i;
110310616SSebastien.Roy@Sun.COM 
110410616SSebastien.Roy@Sun.COM 	for (i = 0; iptun_type_table[i].iti_type != IPTUN_TYPE_UNKNOWN; i++) {
110510616SSebastien.Roy@Sun.COM 		if (iptun_type_table[i].iti_type == type)
110610616SSebastien.Roy@Sun.COM 			break;
110710616SSebastien.Roy@Sun.COM 	}
110810616SSebastien.Roy@Sun.COM 	return (&iptun_type_table[i]);
110910616SSebastien.Roy@Sun.COM }
111010616SSebastien.Roy@Sun.COM 
111110616SSebastien.Roy@Sun.COM /*
111210616SSebastien.Roy@Sun.COM  * Set the parameters included in ik on the tunnel iptun.  Parameters that can
111310616SSebastien.Roy@Sun.COM  * only be set at creation time are set in iptun_create().
111410616SSebastien.Roy@Sun.COM  */
111510616SSebastien.Roy@Sun.COM static int
111610616SSebastien.Roy@Sun.COM iptun_setparams(iptun_t *iptun, const iptun_kparams_t *ik)
111710616SSebastien.Roy@Sun.COM {
111810616SSebastien.Roy@Sun.COM 	int		err = 0;
111910616SSebastien.Roy@Sun.COM 	netstack_t	*ns = iptun->iptun_ns;
112010616SSebastien.Roy@Sun.COM 	iptun_addr_t	orig_laddr, orig_raddr;
112110616SSebastien.Roy@Sun.COM 	uint_t		orig_flags = iptun->iptun_flags;
112210616SSebastien.Roy@Sun.COM 
112310616SSebastien.Roy@Sun.COM 	if (ik->iptun_kparam_flags & IPTUN_KPARAM_LADDR) {
112410616SSebastien.Roy@Sun.COM 		if (orig_flags & IPTUN_LADDR)
112510616SSebastien.Roy@Sun.COM 			orig_laddr = iptun->iptun_laddr;
112610616SSebastien.Roy@Sun.COM 		if ((err = iptun_setladdr(iptun, &ik->iptun_kparam_laddr)) != 0)
112710616SSebastien.Roy@Sun.COM 			return (err);
112810616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_LADDR;
112910616SSebastien.Roy@Sun.COM 	}
113010616SSebastien.Roy@Sun.COM 
113110616SSebastien.Roy@Sun.COM 	if (ik->iptun_kparam_flags & IPTUN_KPARAM_RADDR) {
113210616SSebastien.Roy@Sun.COM 		if (orig_flags & IPTUN_RADDR)
113310616SSebastien.Roy@Sun.COM 			orig_raddr = iptun->iptun_raddr;
113410616SSebastien.Roy@Sun.COM 		if ((err = iptun_setraddr(iptun, &ik->iptun_kparam_raddr)) != 0)
113510616SSebastien.Roy@Sun.COM 			goto done;
113610616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_RADDR;
113710616SSebastien.Roy@Sun.COM 	}
113810616SSebastien.Roy@Sun.COM 
113910616SSebastien.Roy@Sun.COM 	if (ik->iptun_kparam_flags & IPTUN_KPARAM_SECINFO) {
114010616SSebastien.Roy@Sun.COM 		/*
114110616SSebastien.Roy@Sun.COM 		 * Set IPsec policy originating from the ifconfig(1M) command
114210616SSebastien.Roy@Sun.COM 		 * line.  This is traditionally called "simple" policy because
114310616SSebastien.Roy@Sun.COM 		 * the ipsec_req_t (iptun_kparam_secinfo) can only describe a
114410616SSebastien.Roy@Sun.COM 		 * simple policy of "do ESP on everything" and/or "do AH on
114510616SSebastien.Roy@Sun.COM 		 * everything" (as opposed to the rich policy that can be
114610616SSebastien.Roy@Sun.COM 		 * defined with ipsecconf(1M)).
114710616SSebastien.Roy@Sun.COM 		 */
114810616SSebastien.Roy@Sun.COM 		if (iptun->iptun_typeinfo->iti_type == IPTUN_TYPE_6TO4) {
114910616SSebastien.Roy@Sun.COM 			/*
115010616SSebastien.Roy@Sun.COM 			 * Can't set security properties for automatic
115110616SSebastien.Roy@Sun.COM 			 * tunnels.
115210616SSebastien.Roy@Sun.COM 			 */
115310616SSebastien.Roy@Sun.COM 			err = EINVAL;
115410616SSebastien.Roy@Sun.COM 			goto done;
115510616SSebastien.Roy@Sun.COM 		}
115610616SSebastien.Roy@Sun.COM 
115710616SSebastien.Roy@Sun.COM 		if (!ipsec_loaded(ns->netstack_ipsec)) {
115810616SSebastien.Roy@Sun.COM 			/* If IPsec can be loaded, try and load it now. */
115910616SSebastien.Roy@Sun.COM 			if (ipsec_failed(ns->netstack_ipsec)) {
116010616SSebastien.Roy@Sun.COM 				err = EPROTONOSUPPORT;
116110616SSebastien.Roy@Sun.COM 				goto done;
116210616SSebastien.Roy@Sun.COM 			}
116310616SSebastien.Roy@Sun.COM 			ipsec_loader_loadnow(ns->netstack_ipsec);
116410616SSebastien.Roy@Sun.COM 			/*
116510616SSebastien.Roy@Sun.COM 			 * ipsec_loader_loadnow() returns while IPsec is
116610616SSebastien.Roy@Sun.COM 			 * loaded asynchronously.  While a method exists to
116710616SSebastien.Roy@Sun.COM 			 * wait for IPsec to load (ipsec_loader_wait()), it
116810616SSebastien.Roy@Sun.COM 			 * requires use of a STREAMS queue to do a qwait().
116910616SSebastien.Roy@Sun.COM 			 * We're not in STREAMS context here, and so we can't
117010616SSebastien.Roy@Sun.COM 			 * use it.  This is not a problem in practice because
117110616SSebastien.Roy@Sun.COM 			 * in the vast majority of cases, key management and
117210616SSebastien.Roy@Sun.COM 			 * global policy will have loaded before any tunnels
117310616SSebastien.Roy@Sun.COM 			 * are plumbed, and so IPsec will already have been
117410616SSebastien.Roy@Sun.COM 			 * loaded.
117510616SSebastien.Roy@Sun.COM 			 */
117610616SSebastien.Roy@Sun.COM 			err = EAGAIN;
117710616SSebastien.Roy@Sun.COM 			goto done;
117810616SSebastien.Roy@Sun.COM 		}
117910616SSebastien.Roy@Sun.COM 
118010616SSebastien.Roy@Sun.COM 		err = iptun_set_sec_simple(iptun, &ik->iptun_kparam_secinfo);
118110616SSebastien.Roy@Sun.COM 		if (err == 0) {
118210616SSebastien.Roy@Sun.COM 			iptun->iptun_flags |= IPTUN_SIMPLE_POLICY;
118310616SSebastien.Roy@Sun.COM 			iptun->iptun_simple_policy = ik->iptun_kparam_secinfo;
118410616SSebastien.Roy@Sun.COM 		}
118510616SSebastien.Roy@Sun.COM 	}
118610616SSebastien.Roy@Sun.COM done:
118710616SSebastien.Roy@Sun.COM 	if (err != 0) {
118810616SSebastien.Roy@Sun.COM 		/* Restore original source and destination. */
118910616SSebastien.Roy@Sun.COM 		if (ik->iptun_kparam_flags & IPTUN_KPARAM_LADDR &&
119010616SSebastien.Roy@Sun.COM 		    (orig_flags & IPTUN_LADDR))
119110616SSebastien.Roy@Sun.COM 			iptun->iptun_laddr = orig_laddr;
119210616SSebastien.Roy@Sun.COM 		if ((ik->iptun_kparam_flags & IPTUN_KPARAM_RADDR) &&
119310616SSebastien.Roy@Sun.COM 		    (orig_flags & IPTUN_RADDR))
119410616SSebastien.Roy@Sun.COM 			iptun->iptun_raddr = orig_raddr;
119510616SSebastien.Roy@Sun.COM 		iptun->iptun_flags = orig_flags;
119610616SSebastien.Roy@Sun.COM 	}
119710616SSebastien.Roy@Sun.COM 	return (err);
119810616SSebastien.Roy@Sun.COM }
119910616SSebastien.Roy@Sun.COM 
120010616SSebastien.Roy@Sun.COM static int
120110616SSebastien.Roy@Sun.COM iptun_register(iptun_t *iptun)
120210616SSebastien.Roy@Sun.COM {
120310616SSebastien.Roy@Sun.COM 	mac_register_t	*mac;
120410616SSebastien.Roy@Sun.COM 	int		err;
120510616SSebastien.Roy@Sun.COM 
120610616SSebastien.Roy@Sun.COM 	ASSERT(!(iptun->iptun_flags & IPTUN_MAC_REGISTERED));
120710616SSebastien.Roy@Sun.COM 
120810616SSebastien.Roy@Sun.COM 	if ((mac = mac_alloc(MAC_VERSION)) == NULL)
120910616SSebastien.Roy@Sun.COM 		return (EINVAL);
121010616SSebastien.Roy@Sun.COM 
121110616SSebastien.Roy@Sun.COM 	mac->m_type_ident = iptun->iptun_typeinfo->iti_ident;
121210616SSebastien.Roy@Sun.COM 	mac->m_driver = iptun;
121310616SSebastien.Roy@Sun.COM 	mac->m_dip = iptun_dip;
121410616SSebastien.Roy@Sun.COM 	mac->m_instance = (uint_t)-1;
121510616SSebastien.Roy@Sun.COM 	mac->m_src_addr = (uint8_t *)&iptun->iptun_laddr.ia_addr;
121610616SSebastien.Roy@Sun.COM 	mac->m_dst_addr = iptun->iptun_typeinfo->iti_hasraddr ?
121710616SSebastien.Roy@Sun.COM 	    (uint8_t *)&iptun->iptun_raddr.ia_addr : NULL;
121810616SSebastien.Roy@Sun.COM 	mac->m_callbacks = &iptun_m_callbacks;
121910616SSebastien.Roy@Sun.COM 	mac->m_min_sdu = iptun->iptun_typeinfo->iti_minmtu;
122010616SSebastien.Roy@Sun.COM 	mac->m_max_sdu = iptun->iptun_mtu;
122110616SSebastien.Roy@Sun.COM 	if (iptun->iptun_header_size != 0) {
122210616SSebastien.Roy@Sun.COM 		mac->m_pdata = &iptun->iptun_header;
122310616SSebastien.Roy@Sun.COM 		mac->m_pdata_size = iptun->iptun_header_size;
122410616SSebastien.Roy@Sun.COM 	}
122510616SSebastien.Roy@Sun.COM 	if ((err = mac_register(mac, &iptun->iptun_mh)) == 0)
122610616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_MAC_REGISTERED;
122710616SSebastien.Roy@Sun.COM 	mac_free(mac);
122810616SSebastien.Roy@Sun.COM 	return (err);
122910616SSebastien.Roy@Sun.COM }
123010616SSebastien.Roy@Sun.COM 
123110616SSebastien.Roy@Sun.COM static int
123210616SSebastien.Roy@Sun.COM iptun_unregister(iptun_t *iptun)
123310616SSebastien.Roy@Sun.COM {
123410616SSebastien.Roy@Sun.COM 	int err;
123510616SSebastien.Roy@Sun.COM 
123610616SSebastien.Roy@Sun.COM 	ASSERT(iptun->iptun_flags & IPTUN_MAC_REGISTERED);
123710616SSebastien.Roy@Sun.COM 	if ((err = mac_unregister(iptun->iptun_mh)) == 0)
123810616SSebastien.Roy@Sun.COM 		iptun->iptun_flags &= ~IPTUN_MAC_REGISTERED;
123910616SSebastien.Roy@Sun.COM 	return (err);
124010616SSebastien.Roy@Sun.COM }
124110616SSebastien.Roy@Sun.COM 
124210616SSebastien.Roy@Sun.COM static conn_t *
124310616SSebastien.Roy@Sun.COM iptun_conn_create(iptun_t *iptun, netstack_t *ns, cred_t *credp)
124410616SSebastien.Roy@Sun.COM {
124510616SSebastien.Roy@Sun.COM 	conn_t *connp;
124610616SSebastien.Roy@Sun.COM 
124710616SSebastien.Roy@Sun.COM 	if ((connp = ipcl_conn_create(IPCL_IPCCONN, KM_NOSLEEP, ns)) == NULL)
124810616SSebastien.Roy@Sun.COM 		return (NULL);
124910616SSebastien.Roy@Sun.COM 
125010616SSebastien.Roy@Sun.COM 	connp->conn_flags |= IPCL_IPTUN;
125110616SSebastien.Roy@Sun.COM 	connp->conn_iptun = iptun;
125210616SSebastien.Roy@Sun.COM 	connp->conn_recv = iptun_input;
1253*11042SErik.Nordmark@Sun.COM 	connp->conn_recvicmp = iptun_input_icmp;
1254*11042SErik.Nordmark@Sun.COM 	connp->conn_verifyicmp = iptun_verifyicmp;
1255*11042SErik.Nordmark@Sun.COM 
1256*11042SErik.Nordmark@Sun.COM 	/*
1257*11042SErik.Nordmark@Sun.COM 	 * Register iptun_notify to listen to capability changes detected by IP.
1258*11042SErik.Nordmark@Sun.COM 	 * This upcall is made in the context of the call to conn_ip_output.
1259*11042SErik.Nordmark@Sun.COM 	 */
1260*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_notify = iptun_notify;
1261*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_notify_cookie = iptun;
1262*11042SErik.Nordmark@Sun.COM 
126310616SSebastien.Roy@Sun.COM 	/*
126410616SSebastien.Roy@Sun.COM 	 * For exclusive stacks we set conn_zoneid to GLOBAL_ZONEID as is done
126510616SSebastien.Roy@Sun.COM 	 * for all other conn_t's.
126610616SSebastien.Roy@Sun.COM 	 *
126710616SSebastien.Roy@Sun.COM 	 * Note that there's an important distinction between iptun_zoneid and
126810616SSebastien.Roy@Sun.COM 	 * conn_zoneid.  The conn_zoneid is set to GLOBAL_ZONEID in non-global
126910616SSebastien.Roy@Sun.COM 	 * exclusive stack zones to make the ip module believe that the
127010616SSebastien.Roy@Sun.COM 	 * non-global zone is actually a global zone.  Therefore, when
127110616SSebastien.Roy@Sun.COM 	 * interacting with the ip module, we must always use conn_zoneid.
127210616SSebastien.Roy@Sun.COM 	 */
127310616SSebastien.Roy@Sun.COM 	connp->conn_zoneid = (ns->netstack_stackid == GLOBAL_NETSTACKID) ?
127410616SSebastien.Roy@Sun.COM 	    crgetzoneid(credp) : GLOBAL_ZONEID;
127510616SSebastien.Roy@Sun.COM 	connp->conn_cred = credp;
127610616SSebastien.Roy@Sun.COM 	/* crfree() is done in ipcl_conn_destroy(), called by CONN_DEC_REF() */
127710616SSebastien.Roy@Sun.COM 	crhold(connp->conn_cred);
1278*11042SErik.Nordmark@Sun.COM 	connp->conn_cpid = NOPID;
1279*11042SErik.Nordmark@Sun.COM 
1280*11042SErik.Nordmark@Sun.COM 	/* conn_allzones can not be set this early, hence no IPCL_ZONEID */
1281*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_zoneid = connp->conn_zoneid;
128210616SSebastien.Roy@Sun.COM 	ASSERT(connp->conn_ref == 1);
128310616SSebastien.Roy@Sun.COM 
1284*11042SErik.Nordmark@Sun.COM 	/* Cache things in ixa without an extra refhold */
1285*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_cred = connp->conn_cred;
1286*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_cpid = connp->conn_cpid;
1287*11042SErik.Nordmark@Sun.COM 	if (is_system_labeled())
1288*11042SErik.Nordmark@Sun.COM 		connp->conn_ixa->ixa_tsl = crgetlabel(connp->conn_cred);
1289*11042SErik.Nordmark@Sun.COM 
1290*11042SErik.Nordmark@Sun.COM 	/*
1291*11042SErik.Nordmark@Sun.COM 	 * Have conn_ip_output drop packets should our outer source
1292*11042SErik.Nordmark@Sun.COM 	 * go invalid
1293*11042SErik.Nordmark@Sun.COM 	 */
1294*11042SErik.Nordmark@Sun.COM 	connp->conn_ixa->ixa_flags |= IXAF_VERIFY_SOURCE;
1295*11042SErik.Nordmark@Sun.COM 
1296*11042SErik.Nordmark@Sun.COM 	switch (iptun->iptun_typeinfo->iti_ipvers) {
1297*11042SErik.Nordmark@Sun.COM 	case IPV4_VERSION:
1298*11042SErik.Nordmark@Sun.COM 		connp->conn_family = AF_INET6;
1299*11042SErik.Nordmark@Sun.COM 		break;
1300*11042SErik.Nordmark@Sun.COM 	case IPV6_VERSION:
1301*11042SErik.Nordmark@Sun.COM 		connp->conn_family = AF_INET;
1302*11042SErik.Nordmark@Sun.COM 		break;
1303*11042SErik.Nordmark@Sun.COM 	}
130410616SSebastien.Roy@Sun.COM 	mutex_enter(&connp->conn_lock);
130510616SSebastien.Roy@Sun.COM 	connp->conn_state_flags &= ~CONN_INCIPIENT;
130610616SSebastien.Roy@Sun.COM 	mutex_exit(&connp->conn_lock);
130710616SSebastien.Roy@Sun.COM 	return (connp);
130810616SSebastien.Roy@Sun.COM }
130910616SSebastien.Roy@Sun.COM 
131010616SSebastien.Roy@Sun.COM static void
131110616SSebastien.Roy@Sun.COM iptun_conn_destroy(conn_t *connp)
131210616SSebastien.Roy@Sun.COM {
131310616SSebastien.Roy@Sun.COM 	ip_quiesce_conn(connp);
131410616SSebastien.Roy@Sun.COM 	connp->conn_iptun = NULL;
131510616SSebastien.Roy@Sun.COM 	ASSERT(connp->conn_ref == 1);
131610616SSebastien.Roy@Sun.COM 	CONN_DEC_REF(connp);
131710616SSebastien.Roy@Sun.COM }
131810616SSebastien.Roy@Sun.COM 
131910616SSebastien.Roy@Sun.COM static iptun_t *
132010616SSebastien.Roy@Sun.COM iptun_alloc(void)
132110616SSebastien.Roy@Sun.COM {
132210616SSebastien.Roy@Sun.COM 	iptun_t *iptun;
132310616SSebastien.Roy@Sun.COM 
132410616SSebastien.Roy@Sun.COM 	if ((iptun = kmem_cache_alloc(iptun_cache, KM_NOSLEEP)) != NULL) {
132510616SSebastien.Roy@Sun.COM 		bzero(iptun, sizeof (*iptun));
132610616SSebastien.Roy@Sun.COM 		atomic_inc_32(&iptun_tunnelcount);
132710616SSebastien.Roy@Sun.COM 	}
132810616SSebastien.Roy@Sun.COM 	return (iptun);
132910616SSebastien.Roy@Sun.COM }
133010616SSebastien.Roy@Sun.COM 
133110616SSebastien.Roy@Sun.COM static void
133210616SSebastien.Roy@Sun.COM iptun_free(iptun_t *iptun)
133310616SSebastien.Roy@Sun.COM {
133410616SSebastien.Roy@Sun.COM 	ASSERT(iptun->iptun_flags & IPTUN_CONDEMNED);
133510616SSebastien.Roy@Sun.COM 
133610616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_HASH_INSERTED) {
133710616SSebastien.Roy@Sun.COM 		iptun_stack_t	*iptuns = iptun->iptun_iptuns;
133810616SSebastien.Roy@Sun.COM 
133910616SSebastien.Roy@Sun.COM 		mutex_enter(&iptun_hash_lock);
134010616SSebastien.Roy@Sun.COM 		VERIFY(mod_hash_remove(iptun_hash,
134110616SSebastien.Roy@Sun.COM 		    IPTUN_HASH_KEY(iptun->iptun_linkid),
134210616SSebastien.Roy@Sun.COM 		    (mod_hash_val_t *)&iptun) == 0);
134310616SSebastien.Roy@Sun.COM 		mutex_exit(&iptun_hash_lock);
134410616SSebastien.Roy@Sun.COM 		iptun->iptun_flags &= ~IPTUN_HASH_INSERTED;
134510616SSebastien.Roy@Sun.COM 		mutex_enter(&iptuns->iptuns_lock);
134610616SSebastien.Roy@Sun.COM 		list_remove(&iptuns->iptuns_iptunlist, iptun);
134710616SSebastien.Roy@Sun.COM 		mutex_exit(&iptuns->iptuns_lock);
134810616SSebastien.Roy@Sun.COM 	}
134910616SSebastien.Roy@Sun.COM 
135010616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_BOUND)
135110616SSebastien.Roy@Sun.COM 		iptun_unbind(iptun);
135210616SSebastien.Roy@Sun.COM 
135310616SSebastien.Roy@Sun.COM 	/*
135410616SSebastien.Roy@Sun.COM 	 * After iptun_unregister(), there will be no threads executing a
135510616SSebastien.Roy@Sun.COM 	 * downcall from the mac module, including in the tx datapath.
135610616SSebastien.Roy@Sun.COM 	 */
135710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_MAC_REGISTERED)
135810616SSebastien.Roy@Sun.COM 		VERIFY(iptun_unregister(iptun) == 0);
135910616SSebastien.Roy@Sun.COM 
136010616SSebastien.Roy@Sun.COM 	if (iptun->iptun_itp != NULL) {
136110616SSebastien.Roy@Sun.COM 		/*
136210616SSebastien.Roy@Sun.COM 		 * Remove from the AVL tree, AND release the reference iptun_t
136310616SSebastien.Roy@Sun.COM 		 * itself holds on the ITP.
136410616SSebastien.Roy@Sun.COM 		 */
136510616SSebastien.Roy@Sun.COM 		itp_unlink(iptun->iptun_itp, iptun->iptun_ns);
136610616SSebastien.Roy@Sun.COM 		ITP_REFRELE(iptun->iptun_itp, iptun->iptun_ns);
136710616SSebastien.Roy@Sun.COM 		iptun->iptun_itp = NULL;
136810616SSebastien.Roy@Sun.COM 		iptun->iptun_flags &= ~IPTUN_SIMPLE_POLICY;
136910616SSebastien.Roy@Sun.COM 	}
137010616SSebastien.Roy@Sun.COM 
137110616SSebastien.Roy@Sun.COM 	/*
137210616SSebastien.Roy@Sun.COM 	 * After ipcl_conn_destroy(), there will be no threads executing an
137310616SSebastien.Roy@Sun.COM 	 * upcall from ip (i.e., iptun_input()), and it is then safe to free
137410616SSebastien.Roy@Sun.COM 	 * the iptun_t.
137510616SSebastien.Roy@Sun.COM 	 */
137610616SSebastien.Roy@Sun.COM 	if (iptun->iptun_connp != NULL) {
137710616SSebastien.Roy@Sun.COM 		iptun_conn_destroy(iptun->iptun_connp);
137810616SSebastien.Roy@Sun.COM 		iptun->iptun_connp = NULL;
137910616SSebastien.Roy@Sun.COM 	}
138010616SSebastien.Roy@Sun.COM 
138110616SSebastien.Roy@Sun.COM 	kmem_cache_free(iptun_cache, iptun);
138210616SSebastien.Roy@Sun.COM 	atomic_dec_32(&iptun_tunnelcount);
138310616SSebastien.Roy@Sun.COM }
138410616SSebastien.Roy@Sun.COM 
138510616SSebastien.Roy@Sun.COM int
138610616SSebastien.Roy@Sun.COM iptun_create(iptun_kparams_t *ik, cred_t *credp)
138710616SSebastien.Roy@Sun.COM {
138810616SSebastien.Roy@Sun.COM 	iptun_t		*iptun = NULL;
138910616SSebastien.Roy@Sun.COM 	int		err = 0, mherr;
139010616SSebastien.Roy@Sun.COM 	char		linkname[MAXLINKNAMELEN];
139110616SSebastien.Roy@Sun.COM 	ipsec_tun_pol_t	*itp;
139210616SSebastien.Roy@Sun.COM 	netstack_t	*ns = NULL;
139310616SSebastien.Roy@Sun.COM 	iptun_stack_t	*iptuns;
139410616SSebastien.Roy@Sun.COM 	datalink_id_t	tmpid;
139510616SSebastien.Roy@Sun.COM 	zoneid_t	zoneid = crgetzoneid(credp);
139610616SSebastien.Roy@Sun.COM 	boolean_t	link_created = B_FALSE;
139710616SSebastien.Roy@Sun.COM 
139810616SSebastien.Roy@Sun.COM 	/* The tunnel type is mandatory */
139910616SSebastien.Roy@Sun.COM 	if (!(ik->iptun_kparam_flags & IPTUN_KPARAM_TYPE))
140010616SSebastien.Roy@Sun.COM 		return (EINVAL);
140110616SSebastien.Roy@Sun.COM 
140210616SSebastien.Roy@Sun.COM 	/*
140310616SSebastien.Roy@Sun.COM 	 * Is the linkid that the caller wishes to associate with this new
140410616SSebastien.Roy@Sun.COM 	 * tunnel assigned to this zone?
140510616SSebastien.Roy@Sun.COM 	 */
140610616SSebastien.Roy@Sun.COM 	if (zone_check_datalink(&zoneid, ik->iptun_kparam_linkid) != 0) {
140710616SSebastien.Roy@Sun.COM 		if (zoneid != GLOBAL_ZONEID)
140810616SSebastien.Roy@Sun.COM 			return (EINVAL);
140910616SSebastien.Roy@Sun.COM 	} else if (zoneid == GLOBAL_ZONEID) {
141010616SSebastien.Roy@Sun.COM 		return (EINVAL);
141110616SSebastien.Roy@Sun.COM 	}
141210616SSebastien.Roy@Sun.COM 
141310616SSebastien.Roy@Sun.COM 	/*
141410616SSebastien.Roy@Sun.COM 	 * Make sure that we're not trying to create a tunnel that has already
141510616SSebastien.Roy@Sun.COM 	 * been created.
141610616SSebastien.Roy@Sun.COM 	 */
141710616SSebastien.Roy@Sun.COM 	if (iptun_enter_by_linkid(ik->iptun_kparam_linkid, &iptun) == 0) {
141810616SSebastien.Roy@Sun.COM 		iptun_exit(iptun);
141910616SSebastien.Roy@Sun.COM 		iptun = NULL;
142010616SSebastien.Roy@Sun.COM 		err = EEXIST;
142110616SSebastien.Roy@Sun.COM 		goto done;
142210616SSebastien.Roy@Sun.COM 	}
142310616SSebastien.Roy@Sun.COM 
142410616SSebastien.Roy@Sun.COM 	ns = netstack_find_by_cred(credp);
142510616SSebastien.Roy@Sun.COM 	iptuns = ns->netstack_iptun;
142610616SSebastien.Roy@Sun.COM 
142710616SSebastien.Roy@Sun.COM 	if ((iptun = iptun_alloc()) == NULL) {
142810616SSebastien.Roy@Sun.COM 		err = ENOMEM;
142910616SSebastien.Roy@Sun.COM 		goto done;
143010616SSebastien.Roy@Sun.COM 	}
143110616SSebastien.Roy@Sun.COM 
143210616SSebastien.Roy@Sun.COM 	iptun->iptun_linkid = ik->iptun_kparam_linkid;
143310616SSebastien.Roy@Sun.COM 	iptun->iptun_zoneid = zoneid;
143410616SSebastien.Roy@Sun.COM 	iptun->iptun_ns = ns;
143510616SSebastien.Roy@Sun.COM 
143610616SSebastien.Roy@Sun.COM 	iptun->iptun_typeinfo = iptun_gettypeinfo(ik->iptun_kparam_type);
143710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_typeinfo->iti_type == IPTUN_TYPE_UNKNOWN) {
143810616SSebastien.Roy@Sun.COM 		err = EINVAL;
143910616SSebastien.Roy@Sun.COM 		goto done;
144010616SSebastien.Roy@Sun.COM 	}
144110616SSebastien.Roy@Sun.COM 
144210616SSebastien.Roy@Sun.COM 	if (ik->iptun_kparam_flags & IPTUN_KPARAM_IMPLICIT)
144310616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_IMPLICIT;
144410616SSebastien.Roy@Sun.COM 
144510616SSebastien.Roy@Sun.COM 	if ((err = iptun_setparams(iptun, ik)) != 0)
144610616SSebastien.Roy@Sun.COM 		goto done;
144710616SSebastien.Roy@Sun.COM 
144810616SSebastien.Roy@Sun.COM 	iptun->iptun_hoplimit = IPTUN_DEFAULT_HOPLIMIT;
144910616SSebastien.Roy@Sun.COM 	if (iptun->iptun_typeinfo->iti_type == IPTUN_TYPE_IPV6)
145010616SSebastien.Roy@Sun.COM 		iptun->iptun_encaplimit = IPTUN_DEFAULT_ENCAPLIMIT;
145110616SSebastien.Roy@Sun.COM 
145210616SSebastien.Roy@Sun.COM 	iptun_headergen(iptun, B_FALSE);
145310616SSebastien.Roy@Sun.COM 
145410616SSebastien.Roy@Sun.COM 	iptun->iptun_connp = iptun_conn_create(iptun, ns, credp);
145510616SSebastien.Roy@Sun.COM 	if (iptun->iptun_connp == NULL) {
145610616SSebastien.Roy@Sun.COM 		err = ENOMEM;
145710616SSebastien.Roy@Sun.COM 		goto done;
145810616SSebastien.Roy@Sun.COM 	}
145910616SSebastien.Roy@Sun.COM 
146010616SSebastien.Roy@Sun.COM 	iptun->iptun_mtu = iptun->iptun_typeinfo->iti_maxmtu;
146110616SSebastien.Roy@Sun.COM 	iptun->iptun_dpmtu = iptun->iptun_mtu;
146210616SSebastien.Roy@Sun.COM 
146310616SSebastien.Roy@Sun.COM 	/*
146410616SSebastien.Roy@Sun.COM 	 * Find an ITP based on linkname.  If we have parms already set via
146510616SSebastien.Roy@Sun.COM 	 * the iptun_setparams() call above, it may have created an ITP for
146610616SSebastien.Roy@Sun.COM 	 * us.  We always try get_tunnel_policy() for DEBUG correctness
146710616SSebastien.Roy@Sun.COM 	 * checks, and we may wish to refactor this to only check when
146810616SSebastien.Roy@Sun.COM 	 * iptun_itp is NULL.
146910616SSebastien.Roy@Sun.COM 	 */
147010616SSebastien.Roy@Sun.COM 	if ((err = dls_mgmt_get_linkinfo(iptun->iptun_linkid, linkname, NULL,
147110616SSebastien.Roy@Sun.COM 	    NULL, NULL)) != 0)
147210616SSebastien.Roy@Sun.COM 		goto done;
147310616SSebastien.Roy@Sun.COM 	if ((itp = get_tunnel_policy(linkname, ns)) != NULL)
147410616SSebastien.Roy@Sun.COM 		iptun->iptun_itp = itp;
147510616SSebastien.Roy@Sun.COM 
147610616SSebastien.Roy@Sun.COM 	/*
147710616SSebastien.Roy@Sun.COM 	 * See if we have the necessary IP addresses assigned to this tunnel
147810616SSebastien.Roy@Sun.COM 	 * to try and bind them with ip underneath us.  If we're not ready to
147910616SSebastien.Roy@Sun.COM 	 * bind yet, then we'll defer the bind operation until the addresses
148010616SSebastien.Roy@Sun.COM 	 * are modified.
148110616SSebastien.Roy@Sun.COM 	 */
148210616SSebastien.Roy@Sun.COM 	if (iptun_canbind(iptun) && ((err = iptun_bind(iptun)) != 0))
148310616SSebastien.Roy@Sun.COM 		goto done;
148410616SSebastien.Roy@Sun.COM 
148510616SSebastien.Roy@Sun.COM 	if ((err = iptun_register(iptun)) != 0)
148610616SSebastien.Roy@Sun.COM 		goto done;
148710616SSebastien.Roy@Sun.COM 
148810616SSebastien.Roy@Sun.COM 	err = dls_devnet_create(iptun->iptun_mh, iptun->iptun_linkid,
148910616SSebastien.Roy@Sun.COM 	    iptun->iptun_zoneid);
149010616SSebastien.Roy@Sun.COM 	if (err != 0)
149110616SSebastien.Roy@Sun.COM 		goto done;
149210616SSebastien.Roy@Sun.COM 	link_created = B_TRUE;
149310616SSebastien.Roy@Sun.COM 
149410616SSebastien.Roy@Sun.COM 	/*
149510616SSebastien.Roy@Sun.COM 	 * We hash by link-id as that is the key used by all other iptun
149610616SSebastien.Roy@Sun.COM 	 * interfaces (modify, delete, etc.).
149710616SSebastien.Roy@Sun.COM 	 */
149810616SSebastien.Roy@Sun.COM 	if ((mherr = mod_hash_insert(iptun_hash,
149910616SSebastien.Roy@Sun.COM 	    IPTUN_HASH_KEY(iptun->iptun_linkid), (mod_hash_val_t)iptun)) == 0) {
150010616SSebastien.Roy@Sun.COM 		mutex_enter(&iptuns->iptuns_lock);
150110616SSebastien.Roy@Sun.COM 		list_insert_head(&iptuns->iptuns_iptunlist, iptun);
150210616SSebastien.Roy@Sun.COM 		mutex_exit(&iptuns->iptuns_lock);
150310616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_HASH_INSERTED;
150410616SSebastien.Roy@Sun.COM 	} else if (mherr == MH_ERR_NOMEM) {
150510616SSebastien.Roy@Sun.COM 		err = ENOMEM;
150610616SSebastien.Roy@Sun.COM 	} else if (mherr == MH_ERR_DUPLICATE) {
150710616SSebastien.Roy@Sun.COM 		err = EEXIST;
150810616SSebastien.Roy@Sun.COM 	} else {
150910616SSebastien.Roy@Sun.COM 		err = EINVAL;
151010616SSebastien.Roy@Sun.COM 	}
151110616SSebastien.Roy@Sun.COM 
151210616SSebastien.Roy@Sun.COM done:
151310616SSebastien.Roy@Sun.COM 	if (iptun == NULL && ns != NULL)
151410616SSebastien.Roy@Sun.COM 		netstack_rele(ns);
151510616SSebastien.Roy@Sun.COM 	if (err != 0 && iptun != NULL) {
151610616SSebastien.Roy@Sun.COM 		if (link_created) {
151710616SSebastien.Roy@Sun.COM 			(void) dls_devnet_destroy(iptun->iptun_mh, &tmpid,
151810616SSebastien.Roy@Sun.COM 			    B_TRUE);
151910616SSebastien.Roy@Sun.COM 		}
152010616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_CONDEMNED;
152110616SSebastien.Roy@Sun.COM 		iptun_free(iptun);
152210616SSebastien.Roy@Sun.COM 	}
152310616SSebastien.Roy@Sun.COM 	return (err);
152410616SSebastien.Roy@Sun.COM }
152510616SSebastien.Roy@Sun.COM 
152610616SSebastien.Roy@Sun.COM int
152710616SSebastien.Roy@Sun.COM iptun_delete(datalink_id_t linkid, cred_t *credp)
152810616SSebastien.Roy@Sun.COM {
152910616SSebastien.Roy@Sun.COM 	int	err;
153010616SSebastien.Roy@Sun.COM 	iptun_t	*iptun = NULL;
153110616SSebastien.Roy@Sun.COM 
153210616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter_by_linkid(linkid, &iptun)) != 0)
153310616SSebastien.Roy@Sun.COM 		return (err);
153410616SSebastien.Roy@Sun.COM 
153510616SSebastien.Roy@Sun.COM 	/* One cannot delete a tunnel that belongs to another zone. */
153610616SSebastien.Roy@Sun.COM 	if (iptun->iptun_zoneid != crgetzoneid(credp)) {
153710616SSebastien.Roy@Sun.COM 		iptun_exit(iptun);
153810616SSebastien.Roy@Sun.COM 		return (EACCES);
153910616SSebastien.Roy@Sun.COM 	}
154010616SSebastien.Roy@Sun.COM 
154110616SSebastien.Roy@Sun.COM 	/*
154210616SSebastien.Roy@Sun.COM 	 * We need to exit iptun in order to issue calls up the stack such as
154310616SSebastien.Roy@Sun.COM 	 * dls_devnet_destroy().  If we call up while still in iptun, deadlock
154410616SSebastien.Roy@Sun.COM 	 * with calls coming down the stack is possible.  We prevent other
154510616SSebastien.Roy@Sun.COM 	 * threads from entering this iptun after we've exited it by setting
154610616SSebastien.Roy@Sun.COM 	 * the IPTUN_DELETE_PENDING flag.  This will cause callers of
154710616SSebastien.Roy@Sun.COM 	 * iptun_enter() to block waiting on iptun_enter_cv.  The assumption
154810616SSebastien.Roy@Sun.COM 	 * here is that the functions we're calling while IPTUN_DELETE_PENDING
154910616SSebastien.Roy@Sun.COM 	 * is set dont resuult in an iptun_enter() call, as that would result
155010616SSebastien.Roy@Sun.COM 	 * in deadlock.
155110616SSebastien.Roy@Sun.COM 	 */
155210616SSebastien.Roy@Sun.COM 	iptun->iptun_flags |= IPTUN_DELETE_PENDING;
155310616SSebastien.Roy@Sun.COM 
155410616SSebastien.Roy@Sun.COM 	/* Wait for any pending upcall to the mac module to complete. */
155510616SSebastien.Roy@Sun.COM 	while (iptun->iptun_flags & IPTUN_UPCALL_PENDING)
155610616SSebastien.Roy@Sun.COM 		cv_wait(&iptun->iptun_upcall_cv, &iptun->iptun_lock);
155710616SSebastien.Roy@Sun.COM 
155810616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
155910616SSebastien.Roy@Sun.COM 
156010616SSebastien.Roy@Sun.COM 	if ((err = dls_devnet_destroy(iptun->iptun_mh, &linkid, B_TRUE)) == 0) {
156110616SSebastien.Roy@Sun.COM 		/*
156210616SSebastien.Roy@Sun.COM 		 * mac_disable() will fail with EBUSY if there are references
156310616SSebastien.Roy@Sun.COM 		 * to the iptun MAC.  If there are none, then mac_disable()
156410616SSebastien.Roy@Sun.COM 		 * will assure that none can be acquired until the MAC is
156510616SSebastien.Roy@Sun.COM 		 * unregistered.
156610616SSebastien.Roy@Sun.COM 		 *
156710616SSebastien.Roy@Sun.COM 		 * XXX CR 6791335 prevents us from calling mac_disable() prior
156810616SSebastien.Roy@Sun.COM 		 * to dls_devnet_destroy(), so we unfortunately need to
156910616SSebastien.Roy@Sun.COM 		 * attempt to re-create the devnet node if mac_disable()
157010616SSebastien.Roy@Sun.COM 		 * fails.
157110616SSebastien.Roy@Sun.COM 		 */
157210616SSebastien.Roy@Sun.COM 		if ((err = mac_disable(iptun->iptun_mh)) != 0) {
157310616SSebastien.Roy@Sun.COM 			(void) dls_devnet_create(iptun->iptun_mh, linkid,
157410616SSebastien.Roy@Sun.COM 			    iptun->iptun_zoneid);
157510616SSebastien.Roy@Sun.COM 		}
157610616SSebastien.Roy@Sun.COM 	}
157710616SSebastien.Roy@Sun.COM 
157810616SSebastien.Roy@Sun.COM 	/*
157910616SSebastien.Roy@Sun.COM 	 * Now that we know the fate of this iptun_t, we need to clear
158010616SSebastien.Roy@Sun.COM 	 * IPTUN_DELETE_PENDING, and set IPTUN_CONDEMNED if the iptun_t is
158110616SSebastien.Roy@Sun.COM 	 * slated to be freed.  Either way, we need to signal the threads
158210616SSebastien.Roy@Sun.COM 	 * waiting in iptun_enter() so that they can either fail if
158310616SSebastien.Roy@Sun.COM 	 * IPTUN_CONDEMNED is set, or continue if it's not.
158410616SSebastien.Roy@Sun.COM 	 */
158510616SSebastien.Roy@Sun.COM 	mutex_enter(&iptun->iptun_lock);
158610616SSebastien.Roy@Sun.COM 	iptun->iptun_flags &= ~IPTUN_DELETE_PENDING;
158710616SSebastien.Roy@Sun.COM 	if (err == 0)
158810616SSebastien.Roy@Sun.COM 		iptun->iptun_flags |= IPTUN_CONDEMNED;
158910616SSebastien.Roy@Sun.COM 	cv_broadcast(&iptun->iptun_enter_cv);
159010616SSebastien.Roy@Sun.COM 	mutex_exit(&iptun->iptun_lock);
159110616SSebastien.Roy@Sun.COM 
159210616SSebastien.Roy@Sun.COM 	/*
159310616SSebastien.Roy@Sun.COM 	 * Note that there is no danger in calling iptun_free() after having
159410616SSebastien.Roy@Sun.COM 	 * dropped the iptun_lock since callers of iptun_enter() at this point
159510616SSebastien.Roy@Sun.COM 	 * are doing so from iptun_enter_by_linkid() (mac_disable() got rid of
159610616SSebastien.Roy@Sun.COM 	 * threads entering from mac callbacks which call iptun_enter()
159710616SSebastien.Roy@Sun.COM 	 * directly) which holds iptun_hash_lock, and iptun_free() grabs this
159810616SSebastien.Roy@Sun.COM 	 * lock in order to remove the iptun_t from the hash table.
159910616SSebastien.Roy@Sun.COM 	 */
160010616SSebastien.Roy@Sun.COM 	if (err == 0)
160110616SSebastien.Roy@Sun.COM 		iptun_free(iptun);
160210616SSebastien.Roy@Sun.COM 
160310616SSebastien.Roy@Sun.COM 	return (err);
160410616SSebastien.Roy@Sun.COM }
160510616SSebastien.Roy@Sun.COM 
160610616SSebastien.Roy@Sun.COM int
160710616SSebastien.Roy@Sun.COM iptun_modify(const iptun_kparams_t *ik, cred_t *credp)
160810616SSebastien.Roy@Sun.COM {
160910616SSebastien.Roy@Sun.COM 	iptun_t		*iptun;
161010616SSebastien.Roy@Sun.COM 	boolean_t	laddr_change = B_FALSE, raddr_change = B_FALSE;
161110616SSebastien.Roy@Sun.COM 	int		err;
161210616SSebastien.Roy@Sun.COM 
161310616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter_by_linkid(ik->iptun_kparam_linkid, &iptun)) != 0)
161410616SSebastien.Roy@Sun.COM 		return (err);
161510616SSebastien.Roy@Sun.COM 
161610616SSebastien.Roy@Sun.COM 	/* One cannot modify a tunnel that belongs to another zone. */
161710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_zoneid != crgetzoneid(credp)) {
161810616SSebastien.Roy@Sun.COM 		err = EACCES;
161910616SSebastien.Roy@Sun.COM 		goto done;
162010616SSebastien.Roy@Sun.COM 	}
162110616SSebastien.Roy@Sun.COM 
162210616SSebastien.Roy@Sun.COM 	/* The tunnel type cannot be changed */
162310616SSebastien.Roy@Sun.COM 	if (ik->iptun_kparam_flags & IPTUN_KPARAM_TYPE) {
162410616SSebastien.Roy@Sun.COM 		err = EINVAL;
162510616SSebastien.Roy@Sun.COM 		goto done;
162610616SSebastien.Roy@Sun.COM 	}
162710616SSebastien.Roy@Sun.COM 
162810616SSebastien.Roy@Sun.COM 	if ((err = iptun_setparams(iptun, ik)) != 0)
162910616SSebastien.Roy@Sun.COM 		goto done;
163010616SSebastien.Roy@Sun.COM 	iptun_headergen(iptun, B_FALSE);
163110616SSebastien.Roy@Sun.COM 
163210616SSebastien.Roy@Sun.COM 	/*
163310616SSebastien.Roy@Sun.COM 	 * If any of the tunnel's addresses has been modified and the tunnel
163410616SSebastien.Roy@Sun.COM 	 * has the necessary addresses assigned to it, we need to try to bind
163510616SSebastien.Roy@Sun.COM 	 * with ip underneath us.  If we're not ready to bind yet, then we'll
163610616SSebastien.Roy@Sun.COM 	 * try again when the addresses are modified later.
163710616SSebastien.Roy@Sun.COM 	 */
163810616SSebastien.Roy@Sun.COM 	laddr_change = (ik->iptun_kparam_flags & IPTUN_KPARAM_LADDR);
163910616SSebastien.Roy@Sun.COM 	raddr_change = (ik->iptun_kparam_flags & IPTUN_KPARAM_RADDR);
164010616SSebastien.Roy@Sun.COM 	if (laddr_change || raddr_change) {
164110616SSebastien.Roy@Sun.COM 		if (iptun->iptun_flags & IPTUN_BOUND)
164210616SSebastien.Roy@Sun.COM 			iptun_unbind(iptun);
164310616SSebastien.Roy@Sun.COM 		if (iptun_canbind(iptun) && (err = iptun_bind(iptun)) != 0) {
164410616SSebastien.Roy@Sun.COM 			if (laddr_change)
164510616SSebastien.Roy@Sun.COM 				iptun->iptun_flags &= ~IPTUN_LADDR;
164610616SSebastien.Roy@Sun.COM 			if (raddr_change)
164710616SSebastien.Roy@Sun.COM 				iptun->iptun_flags &= ~IPTUN_RADDR;
164810616SSebastien.Roy@Sun.COM 			goto done;
164910616SSebastien.Roy@Sun.COM 		}
165010616SSebastien.Roy@Sun.COM 	}
165110616SSebastien.Roy@Sun.COM 
165210616SSebastien.Roy@Sun.COM 	if (laddr_change)
165310616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_LADDR_UPDATE);
165410616SSebastien.Roy@Sun.COM 	if (raddr_change)
165510616SSebastien.Roy@Sun.COM 		iptun_task_dispatch(iptun, IPTUN_TASK_RADDR_UPDATE);
165610616SSebastien.Roy@Sun.COM 
165710616SSebastien.Roy@Sun.COM done:
165810616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
165910616SSebastien.Roy@Sun.COM 	return (err);
166010616SSebastien.Roy@Sun.COM }
166110616SSebastien.Roy@Sun.COM 
166210616SSebastien.Roy@Sun.COM /* Given an IP tunnel's datalink id, fill in its parameters. */
166310616SSebastien.Roy@Sun.COM int
166410616SSebastien.Roy@Sun.COM iptun_info(iptun_kparams_t *ik, cred_t *credp)
166510616SSebastien.Roy@Sun.COM {
166610616SSebastien.Roy@Sun.COM 	iptun_t	*iptun;
166710616SSebastien.Roy@Sun.COM 	int	err;
166810616SSebastien.Roy@Sun.COM 
166910616SSebastien.Roy@Sun.COM 	/* Is the tunnel link visible from the caller's zone? */
167010616SSebastien.Roy@Sun.COM 	if (!dls_devnet_islinkvisible(ik->iptun_kparam_linkid,
167110616SSebastien.Roy@Sun.COM 	    crgetzoneid(credp)))
167210616SSebastien.Roy@Sun.COM 		return (ENOENT);
167310616SSebastien.Roy@Sun.COM 
167410616SSebastien.Roy@Sun.COM 	if ((err = iptun_enter_by_linkid(ik->iptun_kparam_linkid, &iptun)) != 0)
167510616SSebastien.Roy@Sun.COM 		return (err);
167610616SSebastien.Roy@Sun.COM 
167710616SSebastien.Roy@Sun.COM 	bzero(ik, sizeof (iptun_kparams_t));
167810616SSebastien.Roy@Sun.COM 
167910616SSebastien.Roy@Sun.COM 	ik->iptun_kparam_linkid = iptun->iptun_linkid;
168010616SSebastien.Roy@Sun.COM 	ik->iptun_kparam_type = iptun->iptun_typeinfo->iti_type;
168110616SSebastien.Roy@Sun.COM 	ik->iptun_kparam_flags |= IPTUN_KPARAM_TYPE;
168210616SSebastien.Roy@Sun.COM 
168310616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_LADDR) {
168410616SSebastien.Roy@Sun.COM 		iptun_getaddr(&iptun->iptun_laddr, &ik->iptun_kparam_laddr);
168510616SSebastien.Roy@Sun.COM 		ik->iptun_kparam_flags |= IPTUN_KPARAM_LADDR;
168610616SSebastien.Roy@Sun.COM 	}
168710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_RADDR) {
168810616SSebastien.Roy@Sun.COM 		iptun_getaddr(&iptun->iptun_raddr, &ik->iptun_kparam_raddr);
168910616SSebastien.Roy@Sun.COM 		ik->iptun_kparam_flags |= IPTUN_KPARAM_RADDR;
169010616SSebastien.Roy@Sun.COM 	}
169110616SSebastien.Roy@Sun.COM 
169210616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_IMPLICIT)
169310616SSebastien.Roy@Sun.COM 		ik->iptun_kparam_flags |= IPTUN_KPARAM_IMPLICIT;
169410616SSebastien.Roy@Sun.COM 
169510616SSebastien.Roy@Sun.COM 	if (iptun->iptun_itp != NULL) {
169610616SSebastien.Roy@Sun.COM 		mutex_enter(&iptun->iptun_itp->itp_lock);
169710616SSebastien.Roy@Sun.COM 		if (iptun->iptun_itp->itp_flags & ITPF_P_ACTIVE) {
169810616SSebastien.Roy@Sun.COM 			ik->iptun_kparam_flags |= IPTUN_KPARAM_IPSECPOL;
169910616SSebastien.Roy@Sun.COM 			if (iptun->iptun_flags & IPTUN_SIMPLE_POLICY) {
170010616SSebastien.Roy@Sun.COM 				ik->iptun_kparam_flags |= IPTUN_KPARAM_SECINFO;
170110616SSebastien.Roy@Sun.COM 				ik->iptun_kparam_secinfo =
170210616SSebastien.Roy@Sun.COM 				    iptun->iptun_simple_policy;
170310616SSebastien.Roy@Sun.COM 			}
170410616SSebastien.Roy@Sun.COM 		}
170510616SSebastien.Roy@Sun.COM 		mutex_exit(&iptun->iptun_itp->itp_lock);
170610616SSebastien.Roy@Sun.COM 	}
170710616SSebastien.Roy@Sun.COM 
170810616SSebastien.Roy@Sun.COM done:
170910616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
171010616SSebastien.Roy@Sun.COM 	return (err);
171110616SSebastien.Roy@Sun.COM }
171210616SSebastien.Roy@Sun.COM 
171310616SSebastien.Roy@Sun.COM int
171410616SSebastien.Roy@Sun.COM iptun_set_6to4relay(netstack_t *ns, ipaddr_t relay_addr)
171510616SSebastien.Roy@Sun.COM {
171610616SSebastien.Roy@Sun.COM 	if (relay_addr == INADDR_BROADCAST || CLASSD(relay_addr))
171710616SSebastien.Roy@Sun.COM 		return (EADDRNOTAVAIL);
171810616SSebastien.Roy@Sun.COM 	ns->netstack_iptun->iptuns_relay_rtr_addr = relay_addr;
171910616SSebastien.Roy@Sun.COM 	return (0);
172010616SSebastien.Roy@Sun.COM }
172110616SSebastien.Roy@Sun.COM 
172210616SSebastien.Roy@Sun.COM void
172310616SSebastien.Roy@Sun.COM iptun_get_6to4relay(netstack_t *ns, ipaddr_t *relay_addr)
172410616SSebastien.Roy@Sun.COM {
172510616SSebastien.Roy@Sun.COM 	*relay_addr = ns->netstack_iptun->iptuns_relay_rtr_addr;
172610616SSebastien.Roy@Sun.COM }
172710616SSebastien.Roy@Sun.COM 
172810616SSebastien.Roy@Sun.COM void
172910616SSebastien.Roy@Sun.COM iptun_set_policy(datalink_id_t linkid, ipsec_tun_pol_t *itp)
173010616SSebastien.Roy@Sun.COM {
173110616SSebastien.Roy@Sun.COM 	iptun_t	*iptun;
173210616SSebastien.Roy@Sun.COM 
173310616SSebastien.Roy@Sun.COM 	if (iptun_enter_by_linkid(linkid, &iptun) != 0)
173410616SSebastien.Roy@Sun.COM 		return;
173510616SSebastien.Roy@Sun.COM 	if (iptun->iptun_itp != itp) {
173610616SSebastien.Roy@Sun.COM 		ASSERT(iptun->iptun_itp == NULL);
173710616SSebastien.Roy@Sun.COM 		ITP_REFHOLD(itp);
173810616SSebastien.Roy@Sun.COM 		iptun->iptun_itp = itp;
173910616SSebastien.Roy@Sun.COM 		/* IPsec policy means IPsec overhead, which means lower MTU. */
1740*11042SErik.Nordmark@Sun.COM 		(void) iptun_update_mtu(iptun, NULL, 0);
174110616SSebastien.Roy@Sun.COM 	}
174210616SSebastien.Roy@Sun.COM 	iptun_exit(iptun);
174310616SSebastien.Roy@Sun.COM }
174410616SSebastien.Roy@Sun.COM 
174510616SSebastien.Roy@Sun.COM /*
174610616SSebastien.Roy@Sun.COM  * Obtain the path MTU to the tunnel destination.
1747*11042SErik.Nordmark@Sun.COM  * Can return zero in some cases.
174810616SSebastien.Roy@Sun.COM  */
174910616SSebastien.Roy@Sun.COM static uint32_t
1750*11042SErik.Nordmark@Sun.COM iptun_get_dst_pmtu(iptun_t *iptun, ip_xmit_attr_t *ixa)
175110616SSebastien.Roy@Sun.COM {
175210616SSebastien.Roy@Sun.COM 	uint32_t	pmtu = 0;
1753*11042SErik.Nordmark@Sun.COM 	conn_t		*connp = iptun->iptun_connp;
1754*11042SErik.Nordmark@Sun.COM 	boolean_t	need_rele = B_FALSE;
175510616SSebastien.Roy@Sun.COM 
175610616SSebastien.Roy@Sun.COM 	/*
1757*11042SErik.Nordmark@Sun.COM 	 * We only obtain the pmtu for tunnels that have a remote tunnel
1758*11042SErik.Nordmark@Sun.COM 	 * address.
175910616SSebastien.Roy@Sun.COM 	 */
176010616SSebastien.Roy@Sun.COM 	if (!(iptun->iptun_flags & IPTUN_RADDR))
176110616SSebastien.Roy@Sun.COM 		return (0);
176210616SSebastien.Roy@Sun.COM 
1763*11042SErik.Nordmark@Sun.COM 	if (ixa == NULL) {
1764*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa(connp, B_FALSE);
1765*11042SErik.Nordmark@Sun.COM 		if (ixa == NULL)
1766*11042SErik.Nordmark@Sun.COM 			return (0);
1767*11042SErik.Nordmark@Sun.COM 		need_rele = B_TRUE;
1768*11042SErik.Nordmark@Sun.COM 	}
1769*11042SErik.Nordmark@Sun.COM 	/*
1770*11042SErik.Nordmark@Sun.COM 	 * Guard against ICMP errors before we have sent, as well as against
1771*11042SErik.Nordmark@Sun.COM 	 * and a thread which held conn_ixa.
1772*11042SErik.Nordmark@Sun.COM 	 */
1773*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_ire != NULL) {
1774*11042SErik.Nordmark@Sun.COM 		pmtu = ip_get_pmtu(ixa);
1775*11042SErik.Nordmark@Sun.COM 
1776*11042SErik.Nordmark@Sun.COM 		/*
1777*11042SErik.Nordmark@Sun.COM 		 * For both IPv4 and IPv6 we can have indication that the outer
1778*11042SErik.Nordmark@Sun.COM 		 * header needs fragmentation.
1779*11042SErik.Nordmark@Sun.COM 		 */
1780*11042SErik.Nordmark@Sun.COM 		if (ixa->ixa_flags & IXAF_PMTU_TOO_SMALL) {
1781*11042SErik.Nordmark@Sun.COM 			/* Must allow fragmentation in ip_output */
1782*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags &= ~IXAF_DONTFRAG;
1783*11042SErik.Nordmark@Sun.COM 		} else if (iptun->iptun_typeinfo->iti_type != IPTUN_TYPE_6TO4) {
1784*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags |= IXAF_DONTFRAG;
1785*11042SErik.Nordmark@Sun.COM 		} else {
1786*11042SErik.Nordmark@Sun.COM 			/* ip_get_pmtu might have set this - we don't want it */
1787*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags &= ~IXAF_PMTU_IPV4_DF;
1788*11042SErik.Nordmark@Sun.COM 		}
1789*11042SErik.Nordmark@Sun.COM 	}
1790*11042SErik.Nordmark@Sun.COM 
1791*11042SErik.Nordmark@Sun.COM 	if (need_rele)
1792*11042SErik.Nordmark@Sun.COM 		ixa_refrele(ixa);
1793*11042SErik.Nordmark@Sun.COM 	return (pmtu);
1794*11042SErik.Nordmark@Sun.COM }
1795*11042SErik.Nordmark@Sun.COM 
1796*11042SErik.Nordmark@Sun.COM /*
1797*11042SErik.Nordmark@Sun.COM  * Update the ip_xmit_attr_t to capture the current lower path mtu as known
1798*11042SErik.Nordmark@Sun.COM  * by ip.
1799*11042SErik.Nordmark@Sun.COM  */
1800*11042SErik.Nordmark@Sun.COM static void
1801*11042SErik.Nordmark@Sun.COM iptun_update_dst_pmtu(iptun_t *iptun, ip_xmit_attr_t *ixa)
1802*11042SErik.Nordmark@Sun.COM {
1803*11042SErik.Nordmark@Sun.COM 	uint32_t	pmtu;
1804*11042SErik.Nordmark@Sun.COM 	conn_t		*connp = iptun->iptun_connp;
1805*11042SErik.Nordmark@Sun.COM 	boolean_t	need_rele = B_FALSE;
1806*11042SErik.Nordmark@Sun.COM 
1807*11042SErik.Nordmark@Sun.COM 	/* IXAF_VERIFY_PMTU is not set if we don't have a fixed destination */
1808*11042SErik.Nordmark@Sun.COM 	if (!(iptun->iptun_flags & IPTUN_RADDR))
1809*11042SErik.Nordmark@Sun.COM 		return;
1810*11042SErik.Nordmark@Sun.COM 
1811*11042SErik.Nordmark@Sun.COM 	if (ixa == NULL) {
1812*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa(connp, B_FALSE);
1813*11042SErik.Nordmark@Sun.COM 		if (ixa == NULL)
1814*11042SErik.Nordmark@Sun.COM 			return;
1815*11042SErik.Nordmark@Sun.COM 		need_rele = B_TRUE;
1816*11042SErik.Nordmark@Sun.COM 	}
1817*11042SErik.Nordmark@Sun.COM 	/*
1818*11042SErik.Nordmark@Sun.COM 	 * Guard against ICMP errors before we have sent, as well as against
1819*11042SErik.Nordmark@Sun.COM 	 * and a thread which held conn_ixa.
1820*11042SErik.Nordmark@Sun.COM 	 */
1821*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_ire != NULL) {
1822*11042SErik.Nordmark@Sun.COM 		pmtu = ip_get_pmtu(ixa);
1823*11042SErik.Nordmark@Sun.COM 		/*
1824*11042SErik.Nordmark@Sun.COM 		 * Update ixa_fragsize and ixa_pmtu.
1825*11042SErik.Nordmark@Sun.COM 		 */
1826*11042SErik.Nordmark@Sun.COM 		ixa->ixa_fragsize = ixa->ixa_pmtu = pmtu;
1827*11042SErik.Nordmark@Sun.COM 
1828*11042SErik.Nordmark@Sun.COM 		/*
1829*11042SErik.Nordmark@Sun.COM 		 * For both IPv4 and IPv6 we can have indication that the outer
1830*11042SErik.Nordmark@Sun.COM 		 * header needs fragmentation.
1831*11042SErik.Nordmark@Sun.COM 		 */
1832*11042SErik.Nordmark@Sun.COM 		if (ixa->ixa_flags & IXAF_PMTU_TOO_SMALL) {
1833*11042SErik.Nordmark@Sun.COM 			/* Must allow fragmentation in ip_output */
1834*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags &= ~IXAF_DONTFRAG;
1835*11042SErik.Nordmark@Sun.COM 		} else if (iptun->iptun_typeinfo->iti_type != IPTUN_TYPE_6TO4) {
1836*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags |= IXAF_DONTFRAG;
1837*11042SErik.Nordmark@Sun.COM 		} else {
1838*11042SErik.Nordmark@Sun.COM 			/* ip_get_pmtu might have set this - we don't want it */
1839*11042SErik.Nordmark@Sun.COM 			ixa->ixa_flags &= ~IXAF_PMTU_IPV4_DF;
1840*11042SErik.Nordmark@Sun.COM 		}
1841*11042SErik.Nordmark@Sun.COM 	}
1842*11042SErik.Nordmark@Sun.COM 
1843*11042SErik.Nordmark@Sun.COM 	if (need_rele)
1844*11042SErik.Nordmark@Sun.COM 		ixa_refrele(ixa);
1845*11042SErik.Nordmark@Sun.COM }
1846*11042SErik.Nordmark@Sun.COM 
1847*11042SErik.Nordmark@Sun.COM /*
1848*11042SErik.Nordmark@Sun.COM  * There is nothing that iptun can verify in addition to IP having
1849*11042SErik.Nordmark@Sun.COM  * verified the IP addresses in the fanout.
1850*11042SErik.Nordmark@Sun.COM  */
1851*11042SErik.Nordmark@Sun.COM /* ARGSUSED */
1852*11042SErik.Nordmark@Sun.COM static boolean_t
1853*11042SErik.Nordmark@Sun.COM iptun_verifyicmp(conn_t *connp, void *arg2, icmph_t *icmph, icmp6_t *icmp6,
1854*11042SErik.Nordmark@Sun.COM     ip_recv_attr_t *ira)
1855*11042SErik.Nordmark@Sun.COM {
1856*11042SErik.Nordmark@Sun.COM 	return (B_TRUE);
1857*11042SErik.Nordmark@Sun.COM }
1858*11042SErik.Nordmark@Sun.COM 
1859*11042SErik.Nordmark@Sun.COM /*
1860*11042SErik.Nordmark@Sun.COM  * Notify function registered with ip_xmit_attr_t.
1861*11042SErik.Nordmark@Sun.COM  */
1862*11042SErik.Nordmark@Sun.COM static void
1863*11042SErik.Nordmark@Sun.COM iptun_notify(void *arg, ip_xmit_attr_t *ixa, ixa_notify_type_t ntype,
1864*11042SErik.Nordmark@Sun.COM     ixa_notify_arg_t narg)
1865*11042SErik.Nordmark@Sun.COM {
1866*11042SErik.Nordmark@Sun.COM 	iptun_t		*iptun = (iptun_t *)arg;
1867*11042SErik.Nordmark@Sun.COM 
1868*11042SErik.Nordmark@Sun.COM 	switch (ntype) {
1869*11042SErik.Nordmark@Sun.COM 	case IXAN_PMTU:
1870*11042SErik.Nordmark@Sun.COM 		(void) iptun_update_mtu(iptun, ixa, narg);
187110616SSebastien.Roy@Sun.COM 		break;
187210616SSebastien.Roy@Sun.COM 	}
187310616SSebastien.Roy@Sun.COM }
187410616SSebastien.Roy@Sun.COM 
187510616SSebastien.Roy@Sun.COM /*
187610616SSebastien.Roy@Sun.COM  * Returns the max of old_ovhd and the overhead associated with pol.
187710616SSebastien.Roy@Sun.COM  */
187810616SSebastien.Roy@Sun.COM static uint32_t
187910616SSebastien.Roy@Sun.COM iptun_max_policy_overhead(ipsec_policy_t *pol, uint32_t old_ovhd)
188010616SSebastien.Roy@Sun.COM {
188110616SSebastien.Roy@Sun.COM 	uint32_t new_ovhd = old_ovhd;
188210616SSebastien.Roy@Sun.COM 
188310616SSebastien.Roy@Sun.COM 	while (pol != NULL) {
188410616SSebastien.Roy@Sun.COM 		new_ovhd = max(new_ovhd,
188510616SSebastien.Roy@Sun.COM 		    ipsec_act_ovhd(&pol->ipsp_act->ipa_act));
188610616SSebastien.Roy@Sun.COM 		pol = pol->ipsp_hash.hash_next;
188710616SSebastien.Roy@Sun.COM 	}
188810616SSebastien.Roy@Sun.COM 	return (new_ovhd);
188910616SSebastien.Roy@Sun.COM }
189010616SSebastien.Roy@Sun.COM 
189110616SSebastien.Roy@Sun.COM static uint32_t
189210616SSebastien.Roy@Sun.COM iptun_get_ipsec_overhead(iptun_t *iptun)
189310616SSebastien.Roy@Sun.COM {
189410616SSebastien.Roy@Sun.COM 	ipsec_policy_root_t	*ipr;
189510616SSebastien.Roy@Sun.COM 	ipsec_policy_head_t	*iph;
189610616SSebastien.Roy@Sun.COM 	ipsec_policy_t		*pol;
189710616SSebastien.Roy@Sun.COM 	ipsec_selector_t	sel;
189810616SSebastien.Roy@Sun.COM 	int			i;
189910616SSebastien.Roy@Sun.COM 	uint32_t		ipsec_ovhd = 0;
190010616SSebastien.Roy@Sun.COM 	ipsec_tun_pol_t		*itp = iptun->iptun_itp;
190110616SSebastien.Roy@Sun.COM 	netstack_t		*ns = iptun->iptun_ns;
190210616SSebastien.Roy@Sun.COM 
190310616SSebastien.Roy@Sun.COM 	if (itp == NULL || !(itp->itp_flags & ITPF_P_ACTIVE)) {
190410616SSebastien.Roy@Sun.COM 		/*
190510616SSebastien.Roy@Sun.COM 		 * Consult global policy, just in case.  This will only work
190610616SSebastien.Roy@Sun.COM 		 * if we have both source and destination addresses to work
190710616SSebastien.Roy@Sun.COM 		 * with.
190810616SSebastien.Roy@Sun.COM 		 */
190910616SSebastien.Roy@Sun.COM 		if ((iptun->iptun_flags & (IPTUN_LADDR|IPTUN_RADDR)) !=
191010616SSebastien.Roy@Sun.COM 		    (IPTUN_LADDR|IPTUN_RADDR))
191110616SSebastien.Roy@Sun.COM 			return (0);
191210616SSebastien.Roy@Sun.COM 
191310616SSebastien.Roy@Sun.COM 		iph = ipsec_system_policy(ns);
191410616SSebastien.Roy@Sun.COM 		bzero(&sel, sizeof (sel));
191510616SSebastien.Roy@Sun.COM 		sel.ips_isv4 =
191610616SSebastien.Roy@Sun.COM 		    (iptun->iptun_typeinfo->iti_ipvers == IPV4_VERSION);
191710616SSebastien.Roy@Sun.COM 		switch (iptun->iptun_typeinfo->iti_ipvers) {
191810616SSebastien.Roy@Sun.COM 		case IPV4_VERSION:
191910616SSebastien.Roy@Sun.COM 			sel.ips_local_addr_v4 = iptun->iptun_laddr4;
192010616SSebastien.Roy@Sun.COM 			sel.ips_remote_addr_v4 = iptun->iptun_raddr4;
192110616SSebastien.Roy@Sun.COM 			break;
192210616SSebastien.Roy@Sun.COM 		case IPV6_VERSION:
192310616SSebastien.Roy@Sun.COM 			sel.ips_local_addr_v6 = iptun->iptun_laddr6;
192410616SSebastien.Roy@Sun.COM 			sel.ips_remote_addr_v6 = iptun->iptun_raddr6;
192510616SSebastien.Roy@Sun.COM 			break;
192610616SSebastien.Roy@Sun.COM 		}
192710616SSebastien.Roy@Sun.COM 		/* Check for both IPv4 and IPv6. */
192810616SSebastien.Roy@Sun.COM 		sel.ips_protocol = IPPROTO_ENCAP;
192910616SSebastien.Roy@Sun.COM 		pol = ipsec_find_policy_head(NULL, iph, IPSEC_TYPE_OUTBOUND,
1930*11042SErik.Nordmark@Sun.COM 		    &sel);
193110616SSebastien.Roy@Sun.COM 		if (pol != NULL) {
193210616SSebastien.Roy@Sun.COM 			ipsec_ovhd = ipsec_act_ovhd(&pol->ipsp_act->ipa_act);
1933*11042SErik.Nordmark@Sun.COM 			IPPOL_REFRELE(pol);
193410616SSebastien.Roy@Sun.COM 		}
193510616SSebastien.Roy@Sun.COM 		sel.ips_protocol = IPPROTO_IPV6;
193610616SSebastien.Roy@Sun.COM 		pol = ipsec_find_policy_head(NULL, iph, IPSEC_TYPE_OUTBOUND,
1937*11042SErik.Nordmark@Sun.COM 		    &sel);
193810616SSebastien.Roy@Sun.COM 		if (pol != NULL) {
193910616SSebastien.Roy@Sun.COM 			ipsec_ovhd = max(ipsec_ovhd,
194010616SSebastien.Roy@Sun.COM 			    ipsec_act_ovhd(&pol->ipsp_act->ipa_act));
1941*11042SErik.Nordmark@Sun.COM 			IPPOL_REFRELE(pol);
194210616SSebastien.Roy@Sun.COM 		}
194310616SSebastien.Roy@Sun.COM 		IPPH_REFRELE(iph, ns);
194410616SSebastien.Roy@Sun.COM 	} else {
194510616SSebastien.Roy@Sun.COM 		/*
194610616SSebastien.Roy@Sun.COM 		 * Look through all of the possible IPsec actions for the
194710616SSebastien.Roy@Sun.COM 		 * tunnel, and find the largest potential IPsec overhead.
194810616SSebastien.Roy@Sun.COM 		 */
194910616SSebastien.Roy@Sun.COM 		iph = itp->itp_policy;
195010616SSebastien.Roy@Sun.COM 		rw_enter(&iph->iph_lock, RW_READER);
195110616SSebastien.Roy@Sun.COM 		ipr = &(iph->iph_root[IPSEC_TYPE_OUTBOUND]);
195210616SSebastien.Roy@Sun.COM 		ipsec_ovhd = iptun_max_policy_overhead(
195310616SSebastien.Roy@Sun.COM 		    ipr->ipr_nonhash[IPSEC_AF_V4], 0);
195410616SSebastien.Roy@Sun.COM 		ipsec_ovhd = iptun_max_policy_overhead(
195510616SSebastien.Roy@Sun.COM 		    ipr->ipr_nonhash[IPSEC_AF_V6], ipsec_ovhd);
195610616SSebastien.Roy@Sun.COM 		for (i = 0; i < ipr->ipr_nchains; i++) {
195710616SSebastien.Roy@Sun.COM 			ipsec_ovhd = iptun_max_policy_overhead(
195810616SSebastien.Roy@Sun.COM 			    ipr->ipr_hash[i].hash_head, ipsec_ovhd);
195910616SSebastien.Roy@Sun.COM 		}
196010616SSebastien.Roy@Sun.COM 		rw_exit(&iph->iph_lock);
196110616SSebastien.Roy@Sun.COM 	}
196210616SSebastien.Roy@Sun.COM 
196310616SSebastien.Roy@Sun.COM 	return (ipsec_ovhd);
196410616SSebastien.Roy@Sun.COM }
196510616SSebastien.Roy@Sun.COM 
196610616SSebastien.Roy@Sun.COM /*
1967*11042SErik.Nordmark@Sun.COM  * Calculate and return the maximum possible upper MTU for the given tunnel.
1968*11042SErik.Nordmark@Sun.COM  *
1969*11042SErik.Nordmark@Sun.COM  * If new_pmtu is set then we also need to update the lower path MTU information
1970*11042SErik.Nordmark@Sun.COM  * in the ip_xmit_attr_t. That is needed since we set IXAF_VERIFY_PMTU so that
1971*11042SErik.Nordmark@Sun.COM  * we are notified by conn_ip_output() when the path MTU increases.
197210616SSebastien.Roy@Sun.COM  */
197310616SSebastien.Roy@Sun.COM static uint32_t
1974*11042SErik.Nordmark@Sun.COM iptun_get_maxmtu(iptun_t *iptun, ip_xmit_attr_t *ixa, uint32_t new_pmtu)
197510616SSebastien.Roy@Sun.COM {
197610616SSebastien.Roy@Sun.COM 	size_t		header_size, ipsec_overhead;
197710616SSebastien.Roy@Sun.COM 	uint32_t	maxmtu, pmtu;
197810616SSebastien.Roy@Sun.COM 
197910616SSebastien.Roy@Sun.COM 	/*
198010616SSebastien.Roy@Sun.COM 	 * Start with the path-MTU to the remote address, which is either
198110616SSebastien.Roy@Sun.COM 	 * provided as the new_pmtu argument, or obtained using
198210616SSebastien.Roy@Sun.COM 	 * iptun_get_dst_pmtu().
198310616SSebastien.Roy@Sun.COM 	 */
198410616SSebastien.Roy@Sun.COM 	if (new_pmtu != 0) {
1985*11042SErik.Nordmark@Sun.COM 		if (iptun->iptun_flags & IPTUN_RADDR)
198610616SSebastien.Roy@Sun.COM 			iptun->iptun_dpmtu = new_pmtu;
198710616SSebastien.Roy@Sun.COM 		pmtu = new_pmtu;
198810616SSebastien.Roy@Sun.COM 	} else if (iptun->iptun_flags & IPTUN_RADDR) {
1989*11042SErik.Nordmark@Sun.COM 		if ((pmtu = iptun_get_dst_pmtu(iptun, ixa)) == 0) {
199010616SSebastien.Roy@Sun.COM 			/*
199110616SSebastien.Roy@Sun.COM 			 * We weren't able to obtain the path-MTU of the
199210616SSebastien.Roy@Sun.COM 			 * destination.  Use the previous value.
199310616SSebastien.Roy@Sun.COM 			 */
199410616SSebastien.Roy@Sun.COM 			pmtu = iptun->iptun_dpmtu;
199510616SSebastien.Roy@Sun.COM 		} else {
199610616SSebastien.Roy@Sun.COM 			iptun->iptun_dpmtu = pmtu;
199710616SSebastien.Roy@Sun.COM 		}
199810616SSebastien.Roy@Sun.COM 	} else {
199910616SSebastien.Roy@Sun.COM 		/*
200010616SSebastien.Roy@Sun.COM 		 * We have no path-MTU information to go on, use the maximum
200110616SSebastien.Roy@Sun.COM 		 * possible value.
200210616SSebastien.Roy@Sun.COM 		 */
200310616SSebastien.Roy@Sun.COM 		pmtu = iptun->iptun_typeinfo->iti_maxmtu;
200410616SSebastien.Roy@Sun.COM 	}
200510616SSebastien.Roy@Sun.COM 
200610616SSebastien.Roy@Sun.COM 	/*
200710616SSebastien.Roy@Sun.COM 	 * Now calculate tunneling overhead and subtract that from the
200810616SSebastien.Roy@Sun.COM 	 * path-MTU information obtained above.
200910616SSebastien.Roy@Sun.COM 	 */
201010616SSebastien.Roy@Sun.COM 	if (iptun->iptun_header_size != 0) {
201110616SSebastien.Roy@Sun.COM 		header_size = iptun->iptun_header_size;
201210616SSebastien.Roy@Sun.COM 	} else {
201310616SSebastien.Roy@Sun.COM 		switch (iptun->iptun_typeinfo->iti_ipvers) {
201410616SSebastien.Roy@Sun.COM 		case IPV4_VERSION:
201510616SSebastien.Roy@Sun.COM 			header_size = sizeof (ipha_t);
201610934Ssommerfeld@sun.com 			if (is_system_labeled())
201710934Ssommerfeld@sun.com 				header_size += IP_MAX_OPT_LENGTH;
201810616SSebastien.Roy@Sun.COM 			break;
201910616SSebastien.Roy@Sun.COM 		case IPV6_VERSION:
202010616SSebastien.Roy@Sun.COM 			header_size = sizeof (iptun_ipv6hdrs_t);
202110616SSebastien.Roy@Sun.COM 			break;
202210616SSebastien.Roy@Sun.COM 		}
202310616SSebastien.Roy@Sun.COM 	}
202410616SSebastien.Roy@Sun.COM 
202510616SSebastien.Roy@Sun.COM 	ipsec_overhead = iptun_get_ipsec_overhead(iptun);
202610616SSebastien.Roy@Sun.COM 
202710616SSebastien.Roy@Sun.COM 	maxmtu = pmtu - (header_size + ipsec_overhead);
202810616SSebastien.Roy@Sun.COM 	return (max(maxmtu, iptun->iptun_typeinfo->iti_minmtu));
202910616SSebastien.Roy@Sun.COM }
203010616SSebastien.Roy@Sun.COM 
203110616SSebastien.Roy@Sun.COM /*
2032*11042SErik.Nordmark@Sun.COM  * Re-calculate the tunnel's MTU as seen from above and notify the MAC layer
2033*11042SErik.Nordmark@Sun.COM  * of any change in MTU.  The new_pmtu argument is the new lower path MTU to
2034*11042SErik.Nordmark@Sun.COM  * the tunnel destination to be used in the tunnel MTU calculation.  Passing
2035*11042SErik.Nordmark@Sun.COM  * in 0 for new_pmtu causes the lower path MTU to be dynamically updated using
2036*11042SErik.Nordmark@Sun.COM  * ip_get_pmtu().
203710616SSebastien.Roy@Sun.COM  *
203810616SSebastien.Roy@Sun.COM  * If the calculated tunnel MTU is different than its previous value, then we
203910616SSebastien.Roy@Sun.COM  * notify the MAC layer above us of this change using mac_maxsdu_update().
204010616SSebastien.Roy@Sun.COM  */
204110616SSebastien.Roy@Sun.COM static uint32_t
2042*11042SErik.Nordmark@Sun.COM iptun_update_mtu(iptun_t *iptun, ip_xmit_attr_t *ixa, uint32_t new_pmtu)
204310616SSebastien.Roy@Sun.COM {
204410616SSebastien.Roy@Sun.COM 	uint32_t newmtu;
204510616SSebastien.Roy@Sun.COM 
2046*11042SErik.Nordmark@Sun.COM 	/* We always update the ixa since we might have set IXAF_VERIFY_PMTU */
2047*11042SErik.Nordmark@Sun.COM 	iptun_update_dst_pmtu(iptun, ixa);
2048*11042SErik.Nordmark@Sun.COM 
204910616SSebastien.Roy@Sun.COM 	/*
205010616SSebastien.Roy@Sun.COM 	 * We return the current MTU without updating it if it was pegged to a
205110616SSebastien.Roy@Sun.COM 	 * static value using the MAC_PROP_MTU link property.
205210616SSebastien.Roy@Sun.COM 	 */
205310616SSebastien.Roy@Sun.COM 	if (iptun->iptun_flags & IPTUN_FIXED_MTU)
205410616SSebastien.Roy@Sun.COM 		return (iptun->iptun_mtu);
205510616SSebastien.Roy@Sun.COM 
205610616SSebastien.Roy@Sun.COM 	/* If the MTU isn't fixed, then use the maximum possible value. */
2057*11042SErik.Nordmark@Sun.COM 	newmtu = iptun_get_maxmtu(iptun, ixa, new_pmtu);
205810616SSebastien.Roy@Sun.COM 	/*
205910616SSebastien.Roy@Sun.COM 	 * We only dynamically adjust the tunnel MTU for tunnels with
206010616SSebastien.Roy@Sun.COM 	 * destinations because dynamic MTU calculations are based on the
206110616SSebastien.Roy@Sun.COM 	 * destination path-MTU.
206210616SSebastien.Roy@Sun.COM 	 */
206310616SSebastien.Roy@Sun.COM 	if ((iptun->iptun_flags & IPTUN_RADDR) && newmtu != iptun->iptun_mtu) {
206410616SSebastien.Roy@Sun.COM 		iptun->iptun_mtu = newmtu;
206510616SSebastien.Roy@Sun.COM 		if (iptun->iptun_flags & IPTUN_MAC_REGISTERED)
206610616SSebastien.Roy@Sun.COM 			iptun_task_dispatch(iptun, IPTUN_TASK_MTU_UPDATE);
206710616SSebastien.Roy@Sun.COM 	}
206810616SSebastien.Roy@Sun.COM 
206910616SSebastien.Roy@Sun.COM 	return (newmtu);
207010616SSebastien.Roy@Sun.COM }
207110616SSebastien.Roy@Sun.COM 
207210616SSebastien.Roy@Sun.COM /*
207310616SSebastien.Roy@Sun.COM  * Frees a packet or packet chain and bumps stat for each freed packet.
207410616SSebastien.Roy@Sun.COM  */
207510616SSebastien.Roy@Sun.COM static void
207610616SSebastien.Roy@Sun.COM iptun_drop_pkt(mblk_t *mp, uint64_t *stat)
207710616SSebastien.Roy@Sun.COM {
207810616SSebastien.Roy@Sun.COM 	mblk_t *pktmp;
207910616SSebastien.Roy@Sun.COM 
208010616SSebastien.Roy@Sun.COM 	for (pktmp = mp; pktmp != NULL; pktmp = mp) {
208110616SSebastien.Roy@Sun.COM 		mp = mp->b_next;
208210616SSebastien.Roy@Sun.COM 		pktmp->b_next = NULL;
208310616SSebastien.Roy@Sun.COM 		if (stat != NULL)
208410616SSebastien.Roy@Sun.COM 			atomic_inc_64(stat);
208510616SSebastien.Roy@Sun.COM 		freemsg(pktmp);
208610616SSebastien.Roy@Sun.COM 	}
208710616SSebastien.Roy@Sun.COM }
208810616SSebastien.Roy@Sun.COM 
208910616SSebastien.Roy@Sun.COM /*
209010616SSebastien.Roy@Sun.COM  * Allocate and return a new mblk to hold an IP and ICMP header, and chain the
209110616SSebastien.Roy@Sun.COM  * original packet to its b_cont.  Returns NULL on failure.
209210616SSebastien.Roy@Sun.COM  */
209310616SSebastien.Roy@Sun.COM static mblk_t *
209410616SSebastien.Roy@Sun.COM iptun_build_icmperr(size_t hdrs_size, mblk_t *orig_pkt)
209510616SSebastien.Roy@Sun.COM {
209610616SSebastien.Roy@Sun.COM 	mblk_t *icmperr_mp;
209710616SSebastien.Roy@Sun.COM 
2098*11042SErik.Nordmark@Sun.COM 	if ((icmperr_mp = allocb(hdrs_size, BPRI_MED)) != NULL) {
209910616SSebastien.Roy@Sun.COM 		icmperr_mp->b_wptr += hdrs_size;
210010616SSebastien.Roy@Sun.COM 		/* tack on the offending packet */
210110616SSebastien.Roy@Sun.COM 		icmperr_mp->b_cont = orig_pkt;
210210616SSebastien.Roy@Sun.COM 	}
210310616SSebastien.Roy@Sun.COM 	return (icmperr_mp);
210410616SSebastien.Roy@Sun.COM }
210510616SSebastien.Roy@Sun.COM 
210610616SSebastien.Roy@Sun.COM /*
210710616SSebastien.Roy@Sun.COM  * Transmit an ICMP error.  mp->b_rptr points at the packet to be included in
210810616SSebastien.Roy@Sun.COM  * the ICMP error.
210910616SSebastien.Roy@Sun.COM  */
211010616SSebastien.Roy@Sun.COM static void
2111*11042SErik.Nordmark@Sun.COM iptun_sendicmp_v4(iptun_t *iptun, icmph_t *icmp, ipha_t *orig_ipha, mblk_t *mp,
2112*11042SErik.Nordmark@Sun.COM     ts_label_t *tsl)
211310616SSebastien.Roy@Sun.COM {
211410616SSebastien.Roy@Sun.COM 	size_t	orig_pktsize, hdrs_size;
211510616SSebastien.Roy@Sun.COM 	mblk_t	*icmperr_mp;
211610616SSebastien.Roy@Sun.COM 	ipha_t	*new_ipha;
211710616SSebastien.Roy@Sun.COM 	icmph_t	*new_icmp;
2118*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t	ixas;
2119*11042SErik.Nordmark@Sun.COM 	conn_t	*connp = iptun->iptun_connp;
212010616SSebastien.Roy@Sun.COM 
212110616SSebastien.Roy@Sun.COM 	orig_pktsize = msgdsize(mp);
212210616SSebastien.Roy@Sun.COM 	hdrs_size = sizeof (ipha_t) + sizeof (icmph_t);
212310616SSebastien.Roy@Sun.COM 	if ((icmperr_mp = iptun_build_icmperr(hdrs_size, mp)) == NULL) {
212410616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_noxmtbuf);
212510616SSebastien.Roy@Sun.COM 		return;
212610616SSebastien.Roy@Sun.COM 	}
212710616SSebastien.Roy@Sun.COM 
212810616SSebastien.Roy@Sun.COM 	new_ipha = (ipha_t *)icmperr_mp->b_rptr;
212910616SSebastien.Roy@Sun.COM 	new_icmp = (icmph_t *)(new_ipha + 1);
213010616SSebastien.Roy@Sun.COM 
213110616SSebastien.Roy@Sun.COM 	new_ipha->ipha_version_and_hdr_length = IP_SIMPLE_HDR_VERSION;
213210616SSebastien.Roy@Sun.COM 	new_ipha->ipha_type_of_service = 0;
213310616SSebastien.Roy@Sun.COM 	new_ipha->ipha_ident = 0;
213410616SSebastien.Roy@Sun.COM 	new_ipha->ipha_fragment_offset_and_flags = 0;
213510616SSebastien.Roy@Sun.COM 	new_ipha->ipha_ttl = orig_ipha->ipha_ttl;
213610616SSebastien.Roy@Sun.COM 	new_ipha->ipha_protocol = IPPROTO_ICMP;
213710616SSebastien.Roy@Sun.COM 	new_ipha->ipha_src = orig_ipha->ipha_dst;
213810616SSebastien.Roy@Sun.COM 	new_ipha->ipha_dst = orig_ipha->ipha_src;
213910616SSebastien.Roy@Sun.COM 	new_ipha->ipha_hdr_checksum = 0; /* will be computed by ip */
214010616SSebastien.Roy@Sun.COM 	new_ipha->ipha_length = htons(hdrs_size + orig_pktsize);
214110616SSebastien.Roy@Sun.COM 
214210616SSebastien.Roy@Sun.COM 	*new_icmp = *icmp;
214310616SSebastien.Roy@Sun.COM 	new_icmp->icmph_checksum = 0;
214410616SSebastien.Roy@Sun.COM 	new_icmp->icmph_checksum = IP_CSUM(icmperr_mp, sizeof (ipha_t), 0);
214510616SSebastien.Roy@Sun.COM 
2146*11042SErik.Nordmark@Sun.COM 	bzero(&ixas, sizeof (ixas));
2147*11042SErik.Nordmark@Sun.COM 	ixas.ixa_flags = IXAF_BASIC_SIMPLE_V4;
2148*11042SErik.Nordmark@Sun.COM 	if (new_ipha->ipha_src == INADDR_ANY)
2149*11042SErik.Nordmark@Sun.COM 		ixas.ixa_flags |= IXAF_SET_SOURCE;
2150*11042SErik.Nordmark@Sun.COM 
2151*11042SErik.Nordmark@Sun.COM 	ixas.ixa_zoneid = IPCL_ZONEID(connp);
2152*11042SErik.Nordmark@Sun.COM 	ixas.ixa_ipst = connp->conn_netstack->netstack_ip;
2153*11042SErik.Nordmark@Sun.COM 	ixas.ixa_cred = connp->conn_cred;
2154*11042SErik.Nordmark@Sun.COM 	ixas.ixa_cpid = NOPID;
2155*11042SErik.Nordmark@Sun.COM 	if (is_system_labeled())
2156*11042SErik.Nordmark@Sun.COM 		ixas.ixa_tsl = tsl;
2157*11042SErik.Nordmark@Sun.COM 
2158*11042SErik.Nordmark@Sun.COM 	ixas.ixa_ifindex = 0;
2159*11042SErik.Nordmark@Sun.COM 	ixas.ixa_multicast_ttl = IP_DEFAULT_MULTICAST_TTL;
2160*11042SErik.Nordmark@Sun.COM 
2161*11042SErik.Nordmark@Sun.COM 	(void) ip_output_simple(icmperr_mp, &ixas);
2162*11042SErik.Nordmark@Sun.COM 	ixa_cleanup(&ixas);
216310616SSebastien.Roy@Sun.COM }
216410616SSebastien.Roy@Sun.COM 
216510616SSebastien.Roy@Sun.COM static void
2166*11042SErik.Nordmark@Sun.COM iptun_sendicmp_v6(iptun_t *iptun, icmp6_t *icmp6, ip6_t *orig_ip6h, mblk_t *mp,
2167*11042SErik.Nordmark@Sun.COM     ts_label_t *tsl)
216810616SSebastien.Roy@Sun.COM {
216910616SSebastien.Roy@Sun.COM 	size_t	orig_pktsize, hdrs_size;
217010616SSebastien.Roy@Sun.COM 	mblk_t	*icmp6err_mp;
217110616SSebastien.Roy@Sun.COM 	ip6_t	*new_ip6h;
217210616SSebastien.Roy@Sun.COM 	icmp6_t	*new_icmp6;
2173*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t	ixas;
2174*11042SErik.Nordmark@Sun.COM 	conn_t	*connp = iptun->iptun_connp;
217510616SSebastien.Roy@Sun.COM 
217610616SSebastien.Roy@Sun.COM 	orig_pktsize = msgdsize(mp);
217710616SSebastien.Roy@Sun.COM 	hdrs_size = sizeof (ip6_t) + sizeof (icmp6_t);
217810616SSebastien.Roy@Sun.COM 	if ((icmp6err_mp = iptun_build_icmperr(hdrs_size, mp)) == NULL) {
217910616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_noxmtbuf);
218010616SSebastien.Roy@Sun.COM 		return;
218110616SSebastien.Roy@Sun.COM 	}
218210616SSebastien.Roy@Sun.COM 
218310616SSebastien.Roy@Sun.COM 	new_ip6h = (ip6_t *)icmp6err_mp->b_rptr;
218410616SSebastien.Roy@Sun.COM 	new_icmp6 = (icmp6_t *)(new_ip6h + 1);
218510616SSebastien.Roy@Sun.COM 
218610616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_vcf = orig_ip6h->ip6_vcf;
218710616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_plen = htons(sizeof (icmp6_t) + orig_pktsize);
218810616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_hops = orig_ip6h->ip6_hops;
218910616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_nxt = IPPROTO_ICMPV6;
219010616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_src = orig_ip6h->ip6_dst;
219110616SSebastien.Roy@Sun.COM 	new_ip6h->ip6_dst = orig_ip6h->ip6_src;
219210616SSebastien.Roy@Sun.COM 
219310616SSebastien.Roy@Sun.COM 	*new_icmp6 = *icmp6;
2194*11042SErik.Nordmark@Sun.COM 	/* The checksum is calculated in ip_output_simple and friends. */
219510616SSebastien.Roy@Sun.COM 	new_icmp6->icmp6_cksum = new_ip6h->ip6_plen;
219610616SSebastien.Roy@Sun.COM 
2197*11042SErik.Nordmark@Sun.COM 	bzero(&ixas, sizeof (ixas));
2198*11042SErik.Nordmark@Sun.COM 	ixas.ixa_flags = IXAF_BASIC_SIMPLE_V6;
2199*11042SErik.Nordmark@Sun.COM 	if (IN6_IS_ADDR_UNSPECIFIED(&new_ip6h->ip6_src))
2200*11042SErik.Nordmark@Sun.COM 		ixas.ixa_flags |= IXAF_SET_SOURCE;
2201*11042SErik.Nordmark@Sun.COM 
2202*11042SErik.Nordmark@Sun.COM 	ixas.ixa_zoneid = IPCL_ZONEID(connp);
2203*11042SErik.Nordmark@Sun.COM 	ixas.ixa_ipst = connp->conn_netstack->netstack_ip;
2204*11042SErik.Nordmark@Sun.COM 	ixas.ixa_cred = connp->conn_cred;
2205*11042SErik.Nordmark@Sun.COM 	ixas.ixa_cpid = NOPID;
2206*11042SErik.Nordmark@Sun.COM 	if (is_system_labeled())
2207*11042SErik.Nordmark@Sun.COM 		ixas.ixa_tsl = tsl;
2208*11042SErik.Nordmark@Sun.COM 
2209*11042SErik.Nordmark@Sun.COM 	ixas.ixa_ifindex = 0;
2210*11042SErik.Nordmark@Sun.COM 	ixas.ixa_multicast_ttl = IP_DEFAULT_MULTICAST_TTL;
2211*11042SErik.Nordmark@Sun.COM 
2212*11042SErik.Nordmark@Sun.COM 	(void) ip_output_simple(icmp6err_mp, &ixas);
2213*11042SErik.Nordmark@Sun.COM 	ixa_cleanup(&ixas);
221410616SSebastien.Roy@Sun.COM }
221510616SSebastien.Roy@Sun.COM 
221610616SSebastien.Roy@Sun.COM static void
221710616SSebastien.Roy@Sun.COM iptun_icmp_error_v4(iptun_t *iptun, ipha_t *orig_ipha, mblk_t *mp,
2218*11042SErik.Nordmark@Sun.COM     uint8_t type, uint8_t code, ts_label_t *tsl)
221910616SSebastien.Roy@Sun.COM {
222010616SSebastien.Roy@Sun.COM 	icmph_t icmp;
222110616SSebastien.Roy@Sun.COM 
222210616SSebastien.Roy@Sun.COM 	bzero(&icmp, sizeof (icmp));
222310616SSebastien.Roy@Sun.COM 	icmp.icmph_type = type;
222410616SSebastien.Roy@Sun.COM 	icmp.icmph_code = code;
222510616SSebastien.Roy@Sun.COM 
2226*11042SErik.Nordmark@Sun.COM 	iptun_sendicmp_v4(iptun, &icmp, orig_ipha, mp, tsl);
222710616SSebastien.Roy@Sun.COM }
222810616SSebastien.Roy@Sun.COM 
222910616SSebastien.Roy@Sun.COM static void
223010616SSebastien.Roy@Sun.COM iptun_icmp_fragneeded_v4(iptun_t *iptun, uint32_t newmtu, ipha_t *orig_ipha,
2231*11042SErik.Nordmark@Sun.COM     mblk_t *mp, ts_label_t *tsl)
223210616SSebastien.Roy@Sun.COM {
223310616SSebastien.Roy@Sun.COM 	icmph_t	icmp;
223410616SSebastien.Roy@Sun.COM 
223510616SSebastien.Roy@Sun.COM 	icmp.icmph_type = ICMP_DEST_UNREACHABLE;
223610616SSebastien.Roy@Sun.COM 	icmp.icmph_code = ICMP_FRAGMENTATION_NEEDED;
223710616SSebastien.Roy@Sun.COM 	icmp.icmph_du_zero = 0;
223810616SSebastien.Roy@Sun.COM 	icmp.icmph_du_mtu = htons(newmtu);
223910616SSebastien.Roy@Sun.COM 
2240*11042SErik.Nordmark@Sun.COM 	iptun_sendicmp_v4(iptun, &icmp, orig_ipha, mp, tsl);
224110616SSebastien.Roy@Sun.COM }
224210616SSebastien.Roy@Sun.COM 
224310616SSebastien.Roy@Sun.COM static void
224410616SSebastien.Roy@Sun.COM iptun_icmp_error_v6(iptun_t *iptun, ip6_t *orig_ip6h, mblk_t *mp,
2245*11042SErik.Nordmark@Sun.COM     uint8_t type, uint8_t code, uint32_t offset, ts_label_t *tsl)
224610616SSebastien.Roy@Sun.COM {
224710616SSebastien.Roy@Sun.COM 	icmp6_t icmp6;
224810616SSebastien.Roy@Sun.COM 
224910616SSebastien.Roy@Sun.COM 	bzero(&icmp6, sizeof (icmp6));
225010616SSebastien.Roy@Sun.COM 	icmp6.icmp6_type = type;
225110616SSebastien.Roy@Sun.COM 	icmp6.icmp6_code = code;
225210616SSebastien.Roy@Sun.COM 	if (type == ICMP6_PARAM_PROB)
225310616SSebastien.Roy@Sun.COM 		icmp6.icmp6_pptr = htonl(offset);
225410616SSebastien.Roy@Sun.COM 
2255*11042SErik.Nordmark@Sun.COM 	iptun_sendicmp_v6(iptun, &icmp6, orig_ip6h, mp, tsl);
225610616SSebastien.Roy@Sun.COM }
225710616SSebastien.Roy@Sun.COM 
225810616SSebastien.Roy@Sun.COM static void
225910616SSebastien.Roy@Sun.COM iptun_icmp_toobig_v6(iptun_t *iptun, uint32_t newmtu, ip6_t *orig_ip6h,
2260*11042SErik.Nordmark@Sun.COM     mblk_t *mp, ts_label_t *tsl)
226110616SSebastien.Roy@Sun.COM {
226210616SSebastien.Roy@Sun.COM 	icmp6_t icmp6;
226310616SSebastien.Roy@Sun.COM 
226410616SSebastien.Roy@Sun.COM 	icmp6.icmp6_type = ICMP6_PACKET_TOO_BIG;
226510616SSebastien.Roy@Sun.COM 	icmp6.icmp6_code = 0;
226610616SSebastien.Roy@Sun.COM 	icmp6.icmp6_mtu = htonl(newmtu);
226710616SSebastien.Roy@Sun.COM 
2268*11042SErik.Nordmark@Sun.COM 	iptun_sendicmp_v6(iptun, &icmp6, orig_ip6h, mp, tsl);
226910616SSebastien.Roy@Sun.COM }
227010616SSebastien.Roy@Sun.COM 
227110616SSebastien.Roy@Sun.COM /*
227210616SSebastien.Roy@Sun.COM  * Determines if the packet pointed to by ipha or ip6h is an ICMP error.  The
227310616SSebastien.Roy@Sun.COM  * mp argument is only used to do bounds checking.
227410616SSebastien.Roy@Sun.COM  */
227510616SSebastien.Roy@Sun.COM static boolean_t
227610616SSebastien.Roy@Sun.COM is_icmp_error(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h)
227710616SSebastien.Roy@Sun.COM {
227810616SSebastien.Roy@Sun.COM 	uint16_t hlen;
227910616SSebastien.Roy@Sun.COM 
228010616SSebastien.Roy@Sun.COM 	if (ipha != NULL) {
228110616SSebastien.Roy@Sun.COM 		icmph_t	*icmph;
228210616SSebastien.Roy@Sun.COM 
228310616SSebastien.Roy@Sun.COM 		ASSERT(ip6h == NULL);
228410616SSebastien.Roy@Sun.COM 		if (ipha->ipha_protocol != IPPROTO_ICMP)
228510616SSebastien.Roy@Sun.COM 			return (B_FALSE);
228610616SSebastien.Roy@Sun.COM 
228710616SSebastien.Roy@Sun.COM 		hlen = IPH_HDR_LENGTH(ipha);
228810616SSebastien.Roy@Sun.COM 		icmph = (icmph_t *)((uint8_t *)ipha + hlen);
228910616SSebastien.Roy@Sun.COM 		return (ICMP_IS_ERROR(icmph->icmph_type) ||
229010616SSebastien.Roy@Sun.COM 		    icmph->icmph_type == ICMP_REDIRECT);
229110616SSebastien.Roy@Sun.COM 	} else {
229210616SSebastien.Roy@Sun.COM 		icmp6_t	*icmp6;
229310616SSebastien.Roy@Sun.COM 		uint8_t	*nexthdrp;
229410616SSebastien.Roy@Sun.COM 
229510616SSebastien.Roy@Sun.COM 		ASSERT(ip6h != NULL);
229610616SSebastien.Roy@Sun.COM 		if (!ip_hdr_length_nexthdr_v6(mp, ip6h, &hlen, &nexthdrp) ||
229710616SSebastien.Roy@Sun.COM 		    *nexthdrp != IPPROTO_ICMPV6) {
229810616SSebastien.Roy@Sun.COM 			return (B_FALSE);
229910616SSebastien.Roy@Sun.COM 		}
230010616SSebastien.Roy@Sun.COM 
230110616SSebastien.Roy@Sun.COM 		icmp6 = (icmp6_t *)((uint8_t *)ip6h + hlen);
230210616SSebastien.Roy@Sun.COM 		return (ICMP6_IS_ERROR(icmp6->icmp6_type) ||
230310616SSebastien.Roy@Sun.COM 		    icmp6->icmp6_type == ND_REDIRECT);
230410616SSebastien.Roy@Sun.COM 	}
230510616SSebastien.Roy@Sun.COM }
230610616SSebastien.Roy@Sun.COM 
230710616SSebastien.Roy@Sun.COM /*
230810616SSebastien.Roy@Sun.COM  * Find inner and outer IP headers from a tunneled packet as setup for calls
230910616SSebastien.Roy@Sun.COM  * into ipsec_tun_{in,out}bound().
2310*11042SErik.Nordmark@Sun.COM  * Note that we need to allow the outer header to be in a separate mblk from
2311*11042SErik.Nordmark@Sun.COM  * the inner header.
2312*11042SErik.Nordmark@Sun.COM  * If the caller knows the outer_hlen, the caller passes it in. Otherwise zero.
231310616SSebastien.Roy@Sun.COM  */
231410616SSebastien.Roy@Sun.COM static size_t
2315*11042SErik.Nordmark@Sun.COM iptun_find_headers(mblk_t *mp, size_t outer_hlen, ipha_t **outer4,
2316*11042SErik.Nordmark@Sun.COM     ipha_t **inner4, ip6_t **outer6, ip6_t **inner6)
231710616SSebastien.Roy@Sun.COM {
231810616SSebastien.Roy@Sun.COM 	ipha_t	*ipha;
231910616SSebastien.Roy@Sun.COM 	size_t	first_mblkl = MBLKL(mp);
232010616SSebastien.Roy@Sun.COM 	mblk_t	*inner_mp;
232110616SSebastien.Roy@Sun.COM 
232210616SSebastien.Roy@Sun.COM 	/*
232310616SSebastien.Roy@Sun.COM 	 * Don't bother handling packets that don't have a full IP header in
232410616SSebastien.Roy@Sun.COM 	 * the fist mblk.  For the input path, the ip module ensures that this
232510616SSebastien.Roy@Sun.COM 	 * won't happen, and on the output path, the IP tunneling MAC-type
232610616SSebastien.Roy@Sun.COM 	 * plugins ensure that this also won't happen.
232710616SSebastien.Roy@Sun.COM 	 */
232810616SSebastien.Roy@Sun.COM 	if (first_mblkl < sizeof (ipha_t))
232910616SSebastien.Roy@Sun.COM 		return (0);
233010616SSebastien.Roy@Sun.COM 	ipha = (ipha_t *)(mp->b_rptr);
233110616SSebastien.Roy@Sun.COM 	switch (IPH_HDR_VERSION(ipha)) {
233210616SSebastien.Roy@Sun.COM 	case IPV4_VERSION:
233310616SSebastien.Roy@Sun.COM 		*outer4 = ipha;
233410616SSebastien.Roy@Sun.COM 		*outer6 = NULL;
2335*11042SErik.Nordmark@Sun.COM 		if (outer_hlen == 0)
2336*11042SErik.Nordmark@Sun.COM 			outer_hlen = IPH_HDR_LENGTH(ipha);
233710616SSebastien.Roy@Sun.COM 		break;
233810616SSebastien.Roy@Sun.COM 	case IPV6_VERSION:
233910616SSebastien.Roy@Sun.COM 		*outer4 = NULL;
234010616SSebastien.Roy@Sun.COM 		*outer6 = (ip6_t *)ipha;
2341*11042SErik.Nordmark@Sun.COM 		if (outer_hlen == 0)
2342*11042SErik.Nordmark@Sun.COM 			outer_hlen = ip_hdr_length_v6(mp, (ip6_t *)ipha);
234310616SSebastien.Roy@Sun.COM 		break;
234410616SSebastien.Roy@Sun.COM 	default:
234510616SSebastien.Roy@Sun.COM 		return (0);
234610616SSebastien.Roy@Sun.COM 	}
234710616SSebastien.Roy@Sun.COM 
234810616SSebastien.Roy@Sun.COM 	if (first_mblkl < outer_hlen ||
234910616SSebastien.Roy@Sun.COM 	    (first_mblkl == outer_hlen && mp->b_cont == NULL))
235010616SSebastien.Roy@Sun.COM 		return (0);
235110616SSebastien.Roy@Sun.COM 
235210616SSebastien.Roy@Sun.COM 	/*
235310616SSebastien.Roy@Sun.COM 	 * We don't bother doing a pullup here since the outer header will
235410616SSebastien.Roy@Sun.COM 	 * just get stripped off soon on input anyway.  We just want to ensure
235510616SSebastien.Roy@Sun.COM 	 * that the inner* pointer points to a full header.
235610616SSebastien.Roy@Sun.COM 	 */
235710616SSebastien.Roy@Sun.COM 	if (first_mblkl == outer_hlen) {
235810616SSebastien.Roy@Sun.COM 		inner_mp = mp->b_cont;
235910616SSebastien.Roy@Sun.COM 		ipha = (ipha_t *)inner_mp->b_rptr;
236010616SSebastien.Roy@Sun.COM 	} else {
236110616SSebastien.Roy@Sun.COM 		inner_mp = mp;
236210616SSebastien.Roy@Sun.COM 		ipha = (ipha_t *)(mp->b_rptr + outer_hlen);
236310616SSebastien.Roy@Sun.COM 	}
236410616SSebastien.Roy@Sun.COM 	switch (IPH_HDR_VERSION(ipha)) {
236510616SSebastien.Roy@Sun.COM 	case IPV4_VERSION:
236610616SSebastien.Roy@Sun.COM 		if (inner_mp->b_wptr - (uint8_t *)ipha < sizeof (ipha_t))
236710616SSebastien.Roy@Sun.COM 			return (0);
236810616SSebastien.Roy@Sun.COM 		*inner4 = ipha;
236910616SSebastien.Roy@Sun.COM 		*inner6 = NULL;
237010616SSebastien.Roy@Sun.COM 		break;
237110616SSebastien.Roy@Sun.COM 	case IPV6_VERSION:
237210616SSebastien.Roy@Sun.COM 		if (inner_mp->b_wptr - (uint8_t *)ipha < sizeof (ip6_t))
237310616SSebastien.Roy@Sun.COM 			return (0);
237410616SSebastien.Roy@Sun.COM 		*inner4 = NULL;
237510616SSebastien.Roy@Sun.COM 		*inner6 = (ip6_t *)ipha;
237610616SSebastien.Roy@Sun.COM 		break;
237710616SSebastien.Roy@Sun.COM 	default:
237810616SSebastien.Roy@Sun.COM 		return (0);
237910616SSebastien.Roy@Sun.COM 	}
238010616SSebastien.Roy@Sun.COM 
238110616SSebastien.Roy@Sun.COM 	return (outer_hlen);
238210616SSebastien.Roy@Sun.COM }
238310616SSebastien.Roy@Sun.COM 
238410616SSebastien.Roy@Sun.COM /*
238510616SSebastien.Roy@Sun.COM  * Received ICMP error in response to an X over IPv4 packet that we
238610616SSebastien.Roy@Sun.COM  * transmitted.
238710616SSebastien.Roy@Sun.COM  *
238810616SSebastien.Roy@Sun.COM  * NOTE: "outer" refers to what's inside the ICMP payload.  We will get one of
238910616SSebastien.Roy@Sun.COM  * the following:
239010616SSebastien.Roy@Sun.COM  *
239110616SSebastien.Roy@Sun.COM  * [IPv4(0)][ICMPv4][IPv4(1)][IPv4(2)][ULP]
239210616SSebastien.Roy@Sun.COM  *
239310616SSebastien.Roy@Sun.COM  *	or
239410616SSebastien.Roy@Sun.COM  *
239510616SSebastien.Roy@Sun.COM  * [IPv4(0)][ICMPv4][IPv4(1)][IPv6][ULP]
239610616SSebastien.Roy@Sun.COM  *
239710616SSebastien.Roy@Sun.COM  * And "outer4" will get set to IPv4(1), and inner[46] will correspond to
239810616SSebastien.Roy@Sun.COM  * whatever the very-inner packet is (IPv4(2) or IPv6).
239910616SSebastien.Roy@Sun.COM  */
240010616SSebastien.Roy@Sun.COM static void
2401*11042SErik.Nordmark@Sun.COM iptun_input_icmp_v4(iptun_t *iptun, mblk_t *data_mp, icmph_t *icmph,
2402*11042SErik.Nordmark@Sun.COM     ip_recv_attr_t *ira)
240310616SSebastien.Roy@Sun.COM {
240410616SSebastien.Roy@Sun.COM 	uint8_t	*orig;
240510616SSebastien.Roy@Sun.COM 	ipha_t	*outer4, *inner4;
240610616SSebastien.Roy@Sun.COM 	ip6_t	*outer6, *inner6;
240710616SSebastien.Roy@Sun.COM 	int	outer_hlen;
240810616SSebastien.Roy@Sun.COM 	uint8_t	type, code;
240910616SSebastien.Roy@Sun.COM 
241010616SSebastien.Roy@Sun.COM 	ASSERT(data_mp->b_cont == NULL);
241110616SSebastien.Roy@Sun.COM 	/*
241210616SSebastien.Roy@Sun.COM 	 * Temporarily move b_rptr forward so that iptun_find_headers() can
241310616SSebastien.Roy@Sun.COM 	 * find headers in the ICMP packet payload.
241410616SSebastien.Roy@Sun.COM 	 */
241510616SSebastien.Roy@Sun.COM 	orig = data_mp->b_rptr;
241610616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = (uint8_t *)(icmph + 1);
241710616SSebastien.Roy@Sun.COM 	/*
241810616SSebastien.Roy@Sun.COM 	 * The ip module ensures that ICMP errors contain at least the
241910616SSebastien.Roy@Sun.COM 	 * original IP header (otherwise, the error would never have made it
242010616SSebastien.Roy@Sun.COM 	 * here).
242110616SSebastien.Roy@Sun.COM 	 */
242210616SSebastien.Roy@Sun.COM 	ASSERT(MBLKL(data_mp) >= 0);
2423*11042SErik.Nordmark@Sun.COM 	outer_hlen = iptun_find_headers(data_mp, 0, &outer4, &inner4, &outer6,
242410616SSebastien.Roy@Sun.COM 	    &inner6);
242510616SSebastien.Roy@Sun.COM 	ASSERT(outer6 == NULL);
242610616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = orig;
242710616SSebastien.Roy@Sun.COM 	if (outer_hlen == 0) {
2428*11042SErik.Nordmark@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_ierrors);
242910616SSebastien.Roy@Sun.COM 		return;
243010616SSebastien.Roy@Sun.COM 	}
243110616SSebastien.Roy@Sun.COM 
243210616SSebastien.Roy@Sun.COM 	/* Only ICMP errors due to tunneled packets should reach here. */
243310616SSebastien.Roy@Sun.COM 	ASSERT(outer4->ipha_protocol == IPPROTO_ENCAP ||
243410616SSebastien.Roy@Sun.COM 	    outer4->ipha_protocol == IPPROTO_IPV6);
243510616SSebastien.Roy@Sun.COM 
2436*11042SErik.Nordmark@Sun.COM 	data_mp = ipsec_tun_inbound(ira, data_mp, iptun->iptun_itp,
2437*11042SErik.Nordmark@Sun.COM 	    inner4, inner6, outer4, outer6, -outer_hlen, iptun->iptun_ns);
2438*11042SErik.Nordmark@Sun.COM 	if (data_mp == NULL) {
243910616SSebastien.Roy@Sun.COM 		/* Callee did all of the freeing. */
244010616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_ierrors);
244110616SSebastien.Roy@Sun.COM 		return;
244210616SSebastien.Roy@Sun.COM 	}
244310616SSebastien.Roy@Sun.COM 	/* We should never see reassembled fragment here. */
244410616SSebastien.Roy@Sun.COM 	ASSERT(data_mp->b_next == NULL);
244510616SSebastien.Roy@Sun.COM 
244610616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = (uint8_t *)outer4 + outer_hlen;
244710616SSebastien.Roy@Sun.COM 
244810616SSebastien.Roy@Sun.COM 	/*
244910616SSebastien.Roy@Sun.COM 	 * If the original packet being transmitted was itself an ICMP error,
245010616SSebastien.Roy@Sun.COM 	 * then drop this packet.  We don't want to generate an ICMP error in
245110616SSebastien.Roy@Sun.COM 	 * response to an ICMP error.
245210616SSebastien.Roy@Sun.COM 	 */
245310616SSebastien.Roy@Sun.COM 	if (is_icmp_error(data_mp, inner4, inner6)) {
245410616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_norcvbuf);
245510616SSebastien.Roy@Sun.COM 		return;
245610616SSebastien.Roy@Sun.COM 	}
245710616SSebastien.Roy@Sun.COM 
245810616SSebastien.Roy@Sun.COM 	switch (icmph->icmph_type) {
245910616SSebastien.Roy@Sun.COM 	case ICMP_DEST_UNREACHABLE:
246010616SSebastien.Roy@Sun.COM 		type = (inner4 != NULL ? icmph->icmph_type : ICMP6_DST_UNREACH);
246110616SSebastien.Roy@Sun.COM 		switch (icmph->icmph_code) {
246210616SSebastien.Roy@Sun.COM 		case ICMP_FRAGMENTATION_NEEDED: {
246310616SSebastien.Roy@Sun.COM 			uint32_t newmtu;
246410616SSebastien.Roy@Sun.COM 
246510616SSebastien.Roy@Sun.COM 			/*
246610616SSebastien.Roy@Sun.COM 			 * We reconcile this with the fact that the tunnel may
246710616SSebastien.Roy@Sun.COM 			 * also have IPsec policy by letting iptun_update_mtu
246810616SSebastien.Roy@Sun.COM 			 * take care of it.
246910616SSebastien.Roy@Sun.COM 			 */
2470*11042SErik.Nordmark@Sun.COM 			newmtu = iptun_update_mtu(iptun, NULL,
2471*11042SErik.Nordmark@Sun.COM 			    ntohs(icmph->icmph_du_mtu));
247210616SSebastien.Roy@Sun.COM 
247310616SSebastien.Roy@Sun.COM 			if (inner4 != NULL) {
247410616SSebastien.Roy@Sun.COM 				iptun_icmp_fragneeded_v4(iptun, newmtu, inner4,
2475*11042SErik.Nordmark@Sun.COM 				    data_mp, ira->ira_tsl);
247610616SSebastien.Roy@Sun.COM 			} else {
247710616SSebastien.Roy@Sun.COM 				iptun_icmp_toobig_v6(iptun, newmtu, inner6,
2478*11042SErik.Nordmark@Sun.COM 				    data_mp, ira->ira_tsl);
247910616SSebastien.Roy@Sun.COM 			}
248010616SSebastien.Roy@Sun.COM 			return;
248110616SSebastien.Roy@Sun.COM 		}
248210616SSebastien.Roy@Sun.COM 		case ICMP_DEST_NET_UNREACH_ADMIN:
248310616SSebastien.Roy@Sun.COM 		case ICMP_DEST_HOST_UNREACH_ADMIN:
248410616SSebastien.Roy@Sun.COM 			code = (inner4 != NULL ? ICMP_DEST_NET_UNREACH_ADMIN :
248510616SSebastien.Roy@Sun.COM 			    ICMP6_DST_UNREACH_ADMIN);
248610616SSebastien.Roy@Sun.COM 			break;
248710616SSebastien.Roy@Sun.COM 		default:
248810616SSebastien.Roy@Sun.COM 			code = (inner4 != NULL ? ICMP_HOST_UNREACHABLE :
248910616SSebastien.Roy@Sun.COM 			    ICMP6_DST_UNREACH_ADDR);
249010616SSebastien.Roy@Sun.COM 			break;
249110616SSebastien.Roy@Sun.COM 		}
249210616SSebastien.Roy@Sun.COM 		break;
249310616SSebastien.Roy@Sun.COM 	case ICMP_TIME_EXCEEDED:
249410616SSebastien.Roy@Sun.COM 		if (inner6 != NULL) {
249510616SSebastien.Roy@Sun.COM 			type = ICMP6_TIME_EXCEEDED;
249610616SSebastien.Roy@Sun.COM 			code = 0;
249710616SSebastien.Roy@Sun.COM 		} /* else we're already set. */
249810616SSebastien.Roy@Sun.COM 		break;
249910616SSebastien.Roy@Sun.COM 	case ICMP_PARAM_PROBLEM:
250010616SSebastien.Roy@Sun.COM 		/*
250110616SSebastien.Roy@Sun.COM 		 * This is a problem with the outer header we transmitted.
250210616SSebastien.Roy@Sun.COM 		 * Treat this as an output error.
250310616SSebastien.Roy@Sun.COM 		 */
250410616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_oerrors);
250510616SSebastien.Roy@Sun.COM 		return;
250610616SSebastien.Roy@Sun.COM 	default:
250710616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_norcvbuf);
250810616SSebastien.Roy@Sun.COM 		return;
250910616SSebastien.Roy@Sun.COM 	}
251010616SSebastien.Roy@Sun.COM 
2511*11042SErik.Nordmark@Sun.COM 	if (inner4 != NULL) {
2512*11042SErik.Nordmark@Sun.COM 		iptun_icmp_error_v4(iptun, inner4, data_mp, type, code,
2513*11042SErik.Nordmark@Sun.COM 		    ira->ira_tsl);
2514*11042SErik.Nordmark@Sun.COM 	} else {
2515*11042SErik.Nordmark@Sun.COM 		iptun_icmp_error_v6(iptun, inner6, data_mp, type, code, 0,
2516*11042SErik.Nordmark@Sun.COM 		    ira->ira_tsl);
2517*11042SErik.Nordmark@Sun.COM 	}
251810616SSebastien.Roy@Sun.COM }
251910616SSebastien.Roy@Sun.COM 
252010616SSebastien.Roy@Sun.COM /*
252110616SSebastien.Roy@Sun.COM  * Return B_TRUE if the IPv6 packet pointed to by ip6h contains a Tunnel
252210616SSebastien.Roy@Sun.COM  * Encapsulation Limit destination option.  If there is one, set encaplim_ptr
252310616SSebastien.Roy@Sun.COM  * to point to the option value.
252410616SSebastien.Roy@Sun.COM  */
252510616SSebastien.Roy@Sun.COM static boolean_t
252610616SSebastien.Roy@Sun.COM iptun_find_encaplimit(mblk_t *mp, ip6_t *ip6h, uint8_t **encaplim_ptr)
252710616SSebastien.Roy@Sun.COM {
2528*11042SErik.Nordmark@Sun.COM 	ip_pkt_t	pkt;
252910616SSebastien.Roy@Sun.COM 	uint8_t		*endptr;
253010616SSebastien.Roy@Sun.COM 	ip6_dest_t	*destp;
253110616SSebastien.Roy@Sun.COM 	struct ip6_opt	*optp;
253210616SSebastien.Roy@Sun.COM 
253310616SSebastien.Roy@Sun.COM 	pkt.ipp_fields = 0; /* must be initialized */
2534*11042SErik.Nordmark@Sun.COM 	(void) ip_find_hdr_v6(mp, ip6h, B_FALSE, &pkt, NULL);
253510616SSebastien.Roy@Sun.COM 	if ((pkt.ipp_fields & IPPF_DSTOPTS) != 0) {
253610616SSebastien.Roy@Sun.COM 		destp = pkt.ipp_dstopts;
2537*11042SErik.Nordmark@Sun.COM 	} else if ((pkt.ipp_fields & IPPF_RTHDRDSTOPTS) != 0) {
2538*11042SErik.Nordmark@Sun.COM 		destp = pkt.ipp_rthdrdstopts;
253910616SSebastien.Roy@Sun.COM 	} else {
254010616SSebastien.Roy@Sun.COM 		return (B_FALSE);
254110616SSebastien.Roy@Sun.COM 	}
254210616SSebastien.Roy@Sun.COM 
254310616SSebastien.Roy@Sun.COM 	endptr = (uint8_t *)destp + 8 * (destp->ip6d_len + 1);
254410616SSebastien.Roy@Sun.COM 	optp = (struct ip6_opt *)(destp + 1);
254510616SSebastien.Roy@Sun.COM 	while (endptr - (uint8_t *)optp > sizeof (*optp)) {
254610616SSebastien.Roy@Sun.COM 		if (optp->ip6o_type == IP6OPT_TUNNEL_LIMIT) {
254710616SSebastien.Roy@Sun.COM 			if ((uint8_t *)(optp + 1) >= endptr)
254810616SSebastien.Roy@Sun.COM 				return (B_FALSE);
254910616SSebastien.Roy@Sun.COM 			*encaplim_ptr = (uint8_t *)&optp[1];
255010616SSebastien.Roy@Sun.COM 			return (B_TRUE);
255110616SSebastien.Roy@Sun.COM 		}
255210616SSebastien.Roy@Sun.COM 		optp = (struct ip6_opt *)((uint8_t *)optp + optp->ip6o_len + 2);
255310616SSebastien.Roy@Sun.COM 	}
255410616SSebastien.Roy@Sun.COM 	return (B_FALSE);
255510616SSebastien.Roy@Sun.COM }
255610616SSebastien.Roy@Sun.COM 
255710616SSebastien.Roy@Sun.COM /*
255810616SSebastien.Roy@Sun.COM  * Received ICMPv6 error in response to an X over IPv6 packet that we
255910616SSebastien.Roy@Sun.COM  * transmitted.
256010616SSebastien.Roy@Sun.COM  *
256110616SSebastien.Roy@Sun.COM  * NOTE: "outer" refers to what's inside the ICMP payload.  We will get one of
256210616SSebastien.Roy@Sun.COM  * the following:
256310616SSebastien.Roy@Sun.COM  *
256410616SSebastien.Roy@Sun.COM  * [IPv6(0)][ICMPv6][IPv6(1)][IPv4][ULP]
256510616SSebastien.Roy@Sun.COM  *
256610616SSebastien.Roy@Sun.COM  *	or
256710616SSebastien.Roy@Sun.COM  *
256810616SSebastien.Roy@Sun.COM  * [IPv6(0)][ICMPv6][IPv6(1)][IPv6(2)][ULP]
256910616SSebastien.Roy@Sun.COM  *
257010616SSebastien.Roy@Sun.COM  * And "outer6" will get set to IPv6(1), and inner[46] will correspond to
257110616SSebastien.Roy@Sun.COM  * whatever the very-inner packet is (IPv4 or IPv6(2)).
257210616SSebastien.Roy@Sun.COM  */
257310616SSebastien.Roy@Sun.COM static void
2574*11042SErik.Nordmark@Sun.COM iptun_input_icmp_v6(iptun_t *iptun, mblk_t *data_mp, icmp6_t *icmp6h,
2575*11042SErik.Nordmark@Sun.COM     ip_recv_attr_t *ira)
257610616SSebastien.Roy@Sun.COM {
257710616SSebastien.Roy@Sun.COM 	uint8_t	*orig;
257810616SSebastien.Roy@Sun.COM 	ipha_t	*outer4, *inner4;
257910616SSebastien.Roy@Sun.COM 	ip6_t	*outer6, *inner6;
258010616SSebastien.Roy@Sun.COM 	int	outer_hlen;
258110616SSebastien.Roy@Sun.COM 	uint8_t	type, code;
258210616SSebastien.Roy@Sun.COM 
258310616SSebastien.Roy@Sun.COM 	ASSERT(data_mp->b_cont == NULL);
258410616SSebastien.Roy@Sun.COM 
258510616SSebastien.Roy@Sun.COM 	/*
258610616SSebastien.Roy@Sun.COM 	 * Temporarily move b_rptr forward so that iptun_find_headers() can
258710616SSebastien.Roy@Sun.COM 	 * find IP headers in the ICMP packet payload.
258810616SSebastien.Roy@Sun.COM 	 */
258910616SSebastien.Roy@Sun.COM 	orig = data_mp->b_rptr;
259010616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = (uint8_t *)(icmp6h + 1);
259110616SSebastien.Roy@Sun.COM 	/*
259210616SSebastien.Roy@Sun.COM 	 * The ip module ensures that ICMP errors contain at least the
259310616SSebastien.Roy@Sun.COM 	 * original IP header (otherwise, the error would never have made it
259410616SSebastien.Roy@Sun.COM 	 * here).
259510616SSebastien.Roy@Sun.COM 	 */
259610616SSebastien.Roy@Sun.COM 	ASSERT(MBLKL(data_mp) >= 0);
2597*11042SErik.Nordmark@Sun.COM 	outer_hlen = iptun_find_headers(data_mp, 0, &outer4, &inner4, &outer6,
259810616SSebastien.Roy@Sun.COM 	    &inner6);
259910616SSebastien.Roy@Sun.COM 	ASSERT(outer4 == NULL);
260010616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = orig;	/* Restore r_ptr */
260110616SSebastien.Roy@Sun.COM 	if (outer_hlen == 0) {
2602*11042SErik.Nordmark@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_ierrors);
260310616SSebastien.Roy@Sun.COM 		return;
260410616SSebastien.Roy@Sun.COM 	}
260510616SSebastien.Roy@Sun.COM 
2606*11042SErik.Nordmark@Sun.COM 	data_mp = ipsec_tun_inbound(ira, data_mp, iptun->iptun_itp,
2607*11042SErik.Nordmark@Sun.COM 	    inner4, inner6, outer4, outer6, -outer_hlen, iptun->iptun_ns);
2608*11042SErik.Nordmark@Sun.COM 	if (data_mp == NULL) {
260910616SSebastien.Roy@Sun.COM 		/* Callee did all of the freeing. */
261010616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_ierrors);
261110616SSebastien.Roy@Sun.COM 		return;
261210616SSebastien.Roy@Sun.COM 	}
261310616SSebastien.Roy@Sun.COM 	/* We should never see reassembled fragment here. */
261410616SSebastien.Roy@Sun.COM 	ASSERT(data_mp->b_next == NULL);
261510616SSebastien.Roy@Sun.COM 
261610616SSebastien.Roy@Sun.COM 	data_mp->b_rptr = (uint8_t *)outer6 + outer_hlen;
261710616SSebastien.Roy@Sun.COM 
261810616SSebastien.Roy@Sun.COM 	/*
261910616SSebastien.Roy@Sun.COM 	 * If the original packet being transmitted was itself an ICMP error,
262010616SSebastien.Roy@Sun.COM 	 * then drop this packet.  We don't want to generate an ICMP error in
262110616SSebastien.Roy@Sun.COM 	 * response to an ICMP error.
262210616SSebastien.Roy@Sun.COM 	 */
262310616SSebastien.Roy@Sun.COM 	if (is_icmp_error(data_mp, inner4, inner6)) {
262410616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_norcvbuf);
262510616SSebastien.Roy@Sun.COM 		return;
262610616SSebastien.Roy@Sun.COM 	}
262710616SSebastien.Roy@Sun.COM 
262810616SSebastien.Roy@Sun.COM 	switch (icmp6h->icmp6_type) {
262910616SSebastien.Roy@Sun.COM 	case ICMP6_PARAM_PROB: {
263010616SSebastien.Roy@Sun.COM 		uint8_t *encaplim_ptr;
263110616SSebastien.Roy@Sun.COM 
263210616SSebastien.Roy@Sun.COM 		/*
263310616SSebastien.Roy@Sun.COM 		 * If the ICMPv6 error points to a valid Tunnel Encapsulation
263410616SSebastien.Roy@Sun.COM 		 * Limit option and the limit value is 0, then fall through
263510616SSebastien.Roy@Sun.COM 		 * and send a host unreachable message.  Otherwise, treat the
263610616SSebastien.Roy@Sun.COM 		 * error as an output error, as there must have been a problem
263710616SSebastien.Roy@Sun.COM 		 * with a packet we sent.
263810616SSebastien.Roy@Sun.COM 		 */
263910616SSebastien.Roy@Sun.COM 		if (!iptun_find_encaplimit(data_mp, outer6, &encaplim_ptr) ||
264010616SSebastien.Roy@Sun.COM 		    (icmp6h->icmp6_pptr !=
264110616SSebastien.Roy@Sun.COM 		    ((ptrdiff_t)encaplim_ptr - (ptrdiff_t)outer6)) ||
264210616SSebastien.Roy@Sun.COM 		    *encaplim_ptr != 0) {
264310616SSebastien.Roy@Sun.COM 			iptun_drop_pkt(data_mp, &iptun->iptun_oerrors);
264410616SSebastien.Roy@Sun.COM 			return;
264510616SSebastien.Roy@Sun.COM 		}
264610616SSebastien.Roy@Sun.COM 		/* FALLTHRU */
264710616SSebastien.Roy@Sun.COM 	}
264810616SSebastien.Roy@Sun.COM 	case ICMP6_TIME_EXCEEDED:
264910616SSebastien.Roy@Sun.COM 	case ICMP6_DST_UNREACH:
265010616SSebastien.Roy@Sun.COM 		type = (inner4 != NULL ? ICMP_DEST_UNREACHABLE :
265110616SSebastien.Roy@Sun.COM 		    ICMP6_DST_UNREACH);
265210616SSebastien.Roy@Sun.COM 		code = (inner4 != NULL ? ICMP_HOST_UNREACHABLE :
265310616SSebastien.Roy@Sun.COM 		    ICMP6_DST_UNREACH_ADDR);
265410616SSebastien.Roy@Sun.COM 		break;
265510616SSebastien.Roy@Sun.COM 	case ICMP6_PACKET_TOO_BIG: {
265610616SSebastien.Roy@Sun.COM 		uint32_t newmtu;
265710616SSebastien.Roy@Sun.COM 
265810616SSebastien.Roy@Sun.COM 		/*
265910616SSebastien.Roy@Sun.COM 		 * We reconcile this with the fact that the tunnel may also
266010616SSebastien.Roy@Sun.COM 		 * have IPsec policy by letting iptun_update_mtu take care of
266110616SSebastien.Roy@Sun.COM 		 * it.
266210616SSebastien.Roy@Sun.COM 		 */
2663*11042SErik.Nordmark@Sun.COM 		newmtu = iptun_update_mtu(iptun, NULL,
2664*11042SErik.Nordmark@Sun.COM 		    ntohl(icmp6h->icmp6_mtu));
266510616SSebastien.Roy@Sun.COM 
266610616SSebastien.Roy@Sun.COM 		if (inner4 != NULL) {
266710616SSebastien.Roy@Sun.COM 			iptun_icmp_fragneeded_v4(iptun, newmtu, inner4,
2668*11042SErik.Nordmark@Sun.COM 			    data_mp, ira->ira_tsl);
266910616SSebastien.Roy@Sun.COM 		} else {
2670*11042SErik.Nordmark@Sun.COM 			iptun_icmp_toobig_v6(iptun, newmtu, inner6, data_mp,
2671*11042SErik.Nordmark@Sun.COM 			    ira->ira_tsl);
267210616SSebastien.Roy@Sun.COM 		}
267310616SSebastien.Roy@Sun.COM 		return;
267410616SSebastien.Roy@Sun.COM 	}
267510616SSebastien.Roy@Sun.COM 	default:
267610616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(data_mp, &iptun->iptun_norcvbuf);
267710616SSebastien.Roy@Sun.COM 		return;
267810616SSebastien.Roy@Sun.COM 	}
267910616SSebastien.Roy@Sun.COM 
2680*11042SErik.Nordmark@Sun.COM 	if (inner4 != NULL) {
2681*11042SErik.Nordmark@Sun.COM 		iptun_icmp_error_v4(iptun, inner4, data_mp, type, code,
2682*11042SErik.Nordmark@Sun.COM 		    ira->ira_tsl);
2683*11042SErik.Nordmark@Sun.COM 	} else {
2684*11042SErik.Nordmark@Sun.COM 		iptun_icmp_error_v6(iptun, inner6, data_mp, type, code, 0,
2685*11042SErik.Nordmark@Sun.COM 		    ira->ira_tsl);
2686*11042SErik.Nordmark@Sun.COM 	}
268710616SSebastien.Roy@Sun.COM }
268810616SSebastien.Roy@Sun.COM 
2689*11042SErik.Nordmark@Sun.COM /*
2690*11042SErik.Nordmark@Sun.COM  * Called as conn_recvicmp from IP for ICMP errors.
2691*11042SErik.Nordmark@Sun.COM  */
2692*11042SErik.Nordmark@Sun.COM /* ARGSUSED2 */
269310616SSebastien.Roy@Sun.COM static void
2694*11042SErik.Nordmark@Sun.COM iptun_input_icmp(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
269510616SSebastien.Roy@Sun.COM {
2696*11042SErik.Nordmark@Sun.COM 	conn_t		*connp = arg;
2697*11042SErik.Nordmark@Sun.COM 	iptun_t		*iptun = connp->conn_iptun;
2698*11042SErik.Nordmark@Sun.COM 	mblk_t		*tmpmp;
2699*11042SErik.Nordmark@Sun.COM 	size_t		hlen;
2700*11042SErik.Nordmark@Sun.COM 
2701*11042SErik.Nordmark@Sun.COM 	ASSERT(IPCL_IS_IPTUN(connp));
2702*11042SErik.Nordmark@Sun.COM 
2703*11042SErik.Nordmark@Sun.COM 	if (mp->b_cont != NULL) {
270410616SSebastien.Roy@Sun.COM 		/*
270510616SSebastien.Roy@Sun.COM 		 * Since ICMP error processing necessitates access to bits
270610616SSebastien.Roy@Sun.COM 		 * that are within the ICMP error payload (the original packet
270710616SSebastien.Roy@Sun.COM 		 * that caused the error), pull everything up into a single
270810616SSebastien.Roy@Sun.COM 		 * block for convenience.
270910616SSebastien.Roy@Sun.COM 		 */
2710*11042SErik.Nordmark@Sun.COM 		if ((tmpmp = msgpullup(mp, -1)) == NULL) {
2711*11042SErik.Nordmark@Sun.COM 			iptun_drop_pkt(mp, &iptun->iptun_norcvbuf);
271210616SSebastien.Roy@Sun.COM 			return;
271310616SSebastien.Roy@Sun.COM 		}
2714*11042SErik.Nordmark@Sun.COM 		freemsg(mp);
2715*11042SErik.Nordmark@Sun.COM 		mp = tmpmp;
271610616SSebastien.Roy@Sun.COM 	}
271710616SSebastien.Roy@Sun.COM 
2718*11042SErik.Nordmark@Sun.COM 	hlen = ira->ira_ip_hdr_length;
271910616SSebastien.Roy@Sun.COM 	switch (iptun->iptun_typeinfo->iti_ipvers) {
272010616SSebastien.Roy@Sun.COM 	case IPV4_VERSION:
272110616SSebastien.Roy@Sun.COM 		/*
272210616SSebastien.Roy@Sun.COM 		 * The outer IP header coming up from IP is always ipha_t
272310616SSebastien.Roy@Sun.COM 		 * alligned (otherwise, we would have crashed in ip).
272410616SSebastien.Roy@Sun.COM 		 */
2725*11042SErik.Nordmark@Sun.COM 		iptun_input_icmp_v4(iptun, mp, (icmph_t *)(mp->b_rptr + hlen),
2726*11042SErik.Nordmark@Sun.COM 		    ira);
272710616SSebastien.Roy@Sun.COM 		break;
272810616SSebastien.Roy@Sun.COM 	case IPV6_VERSION:
2729*11042SErik.Nordmark@Sun.COM 		iptun_input_icmp_v6(iptun, mp, (icmp6_t *)(mp->b_rptr + hlen),
2730*11042SErik.Nordmark@Sun.COM 		    ira);
273110616SSebastien.Roy@Sun.COM 		break;
273210616SSebastien.Roy@Sun.COM 	}
273310616SSebastien.Roy@Sun.COM }
273410616SSebastien.Roy@Sun.COM 
273510616SSebastien.Roy@Sun.COM static boolean_t
273610616SSebastien.Roy@Sun.COM iptun_in_6to4_ok(iptun_t *iptun, ipha_t *outer4, ip6_t *inner6)
273710616SSebastien.Roy@Sun.COM {
273810616SSebastien.Roy@Sun.COM 	ipaddr_t v4addr;
273910616SSebastien.Roy@Sun.COM 
274010616SSebastien.Roy@Sun.COM 	/*
274110722SSebastien.Roy@Sun.COM 	 * It's possible that someone sent us an IPv4-in-IPv4 packet with the
274210722SSebastien.Roy@Sun.COM 	 * IPv4 address of a 6to4 tunnel as the destination.
274310722SSebastien.Roy@Sun.COM 	 */
274410722SSebastien.Roy@Sun.COM 	if (inner6 == NULL)
274510722SSebastien.Roy@Sun.COM 		return (B_FALSE);
274610722SSebastien.Roy@Sun.COM 
274710722SSebastien.Roy@Sun.COM 	/*
274810616SSebastien.Roy@Sun.COM 	 * Make sure that the IPv6 destination is within the site that this
274910616SSebastien.Roy@Sun.COM 	 * 6to4 tunnel is routing for.  We don't want people bouncing random
275010616SSebastien.Roy@Sun.COM 	 * tunneled IPv6 packets through this 6to4 router.
275110616SSebastien.Roy@Sun.COM 	 */
275210616SSebastien.Roy@Sun.COM 	IN6_6TO4_TO_V4ADDR(&inner6->ip6_dst, (struct in_addr *)&v4addr);
275310616SSebastien.Roy@Sun.COM 	if (outer4->ipha_dst != v4addr)
275410616SSebastien.Roy@Sun.COM 		return (B_FALSE);
275510616SSebastien.Roy@Sun.COM 
275610616SSebastien.Roy@Sun.COM 	if (IN6_IS_ADDR_6TO4(&inner6->ip6_src)) {
275710616SSebastien.Roy@Sun.COM 		/*
275810616SSebastien.Roy@Sun.COM 		 * Section 9 of RFC 3056 (security considerations) suggests
275910616SSebastien.Roy@Sun.COM 		 * that when a packet is from a 6to4 site (i.e., it's not a
276010616SSebastien.Roy@Sun.COM 		 * global address being forwarded froma relay router), make
276110616SSebastien.Roy@Sun.COM 		 * sure that the packet was tunneled by that site's 6to4
276210616SSebastien.Roy@Sun.COM 		 * router.
276310616SSebastien.Roy@Sun.COM 		 */
276410616SSebastien.Roy@Sun.COM 		IN6_6TO4_TO_V4ADDR(&inner6->ip6_src, (struct in_addr *)&v4addr);
276510616SSebastien.Roy@Sun.COM 		if (outer4->ipha_src != v4addr)
276610616SSebastien.Roy@Sun.COM 			return (B_FALSE);
276710616SSebastien.Roy@Sun.COM 	} else {
276810616SSebastien.Roy@Sun.COM 		/*
276910616SSebastien.Roy@Sun.COM 		 * Only accept packets from a relay router if we've configured
277010616SSebastien.Roy@Sun.COM 		 * outbound relay router functionality.
277110616SSebastien.Roy@Sun.COM 		 */
277210616SSebastien.Roy@Sun.COM 		if (iptun->iptun_iptuns->iptuns_relay_rtr_addr == INADDR_ANY)
277310616SSebastien.Roy@Sun.COM 			return (B_FALSE);
277410616SSebastien.Roy@Sun.COM 	}
277510616SSebastien.Roy@Sun.COM 
277610616SSebastien.Roy@Sun.COM 	return (B_TRUE);
277710616SSebastien.Roy@Sun.COM }
277810616SSebastien.Roy@Sun.COM 
277910616SSebastien.Roy@Sun.COM /*
278010616SSebastien.Roy@Sun.COM  * Input function for everything that comes up from the ip module below us.
278110616SSebastien.Roy@Sun.COM  * This is called directly from the ip module via connp->conn_recv().
278210616SSebastien.Roy@Sun.COM  *
2783*11042SErik.Nordmark@Sun.COM  * We receive M_DATA messages with IP-in-IP tunneled packets.
278410616SSebastien.Roy@Sun.COM  */
2785*11042SErik.Nordmark@Sun.COM /* ARGSUSED2 */
278610616SSebastien.Roy@Sun.COM static void
2787*11042SErik.Nordmark@Sun.COM iptun_input(void *arg, mblk_t *data_mp, void *arg2, ip_recv_attr_t *ira)
278810616SSebastien.Roy@Sun.COM {
278910616SSebastien.Roy@Sun.COM 	conn_t	*connp = arg;
279010616SSebastien.Roy@Sun.COM 	iptun_t	*iptun = connp->conn_iptun;
279110616SSebastien.Roy@Sun.COM 	int	outer_hlen;
279210616SSebastien.Roy@Sun.COM 	ipha_t	*outer4, *inner4;
279310616SSebastien.Roy@Sun.COM 	ip6_t	*outer6, *inner6;
279410616SSebastien.Roy@Sun.COM 
279510616SSebastien.Roy@Sun.COM 	ASSERT(IPCL_IS_IPTUN(connp));
2796*11042SErik.Nordmark@Sun.COM 	ASSERT(DB_TYPE(data_mp) == M_DATA);
2797*11042SErik.Nordmark@Sun.COM 
2798*11042SErik.Nordmark@Sun.COM 	outer_hlen = iptun_find_headers(data_mp, ira->ira_ip_hdr_length,
2799*11042SErik.Nordmark@Sun.COM 	    &outer4, &inner4, &outer6, &inner6);
2800*11042SErik.Nordmark@Sun.COM 	if (outer_hlen == 0)
280110616SSebastien.Roy@Sun.COM 		goto drop;
280210616SSebastien.Roy@Sun.COM 
280310616SSebastien.Roy@Sun.COM 	/*
280410616SSebastien.Roy@Sun.COM 	 * If the system is labeled, we call tsol_check_dest() on the packet
280510616SSebastien.Roy@Sun.COM 	 * destination (our local tunnel address) to ensure that the packet as
280610616SSebastien.Roy@Sun.COM 	 * labeled should be allowed to be sent to us.  We don't need to call
280710616SSebastien.Roy@Sun.COM 	 * the more involved tsol_receive_local() since the tunnel link itself
280810616SSebastien.Roy@Sun.COM 	 * cannot be assigned to shared-stack non-global zones.
280910616SSebastien.Roy@Sun.COM 	 */
2810*11042SErik.Nordmark@Sun.COM 	if (ira->ira_flags & IRAF_SYSTEM_LABELED) {
2811*11042SErik.Nordmark@Sun.COM 		if (ira->ira_tsl == NULL)
281210616SSebastien.Roy@Sun.COM 			goto drop;
2813*11042SErik.Nordmark@Sun.COM 		if (tsol_check_dest(ira->ira_tsl, (outer4 != NULL ?
281410616SSebastien.Roy@Sun.COM 		    (void *)&outer4->ipha_dst : (void *)&outer6->ip6_dst),
281510616SSebastien.Roy@Sun.COM 		    (outer4 != NULL ? IPV4_VERSION : IPV6_VERSION),
2816*11042SErik.Nordmark@Sun.COM 		    CONN_MAC_DEFAULT, B_FALSE, NULL) != 0)
281710616SSebastien.Roy@Sun.COM 			goto drop;
281810616SSebastien.Roy@Sun.COM 	}
281910616SSebastien.Roy@Sun.COM 
2820*11042SErik.Nordmark@Sun.COM 	data_mp = ipsec_tun_inbound(ira, data_mp, iptun->iptun_itp,
2821*11042SErik.Nordmark@Sun.COM 	    inner4, inner6, outer4, outer6, outer_hlen, iptun->iptun_ns);
2822*11042SErik.Nordmark@Sun.COM 	if (data_mp == NULL) {
282310692Sdanmcd@sun.com 		/* Callee did all of the freeing. */
282410692Sdanmcd@sun.com 		return;
282510616SSebastien.Roy@Sun.COM 	}
282610616SSebastien.Roy@Sun.COM 
282710616SSebastien.Roy@Sun.COM 	if (iptun->iptun_typeinfo->iti_type == IPTUN_TYPE_6TO4 &&
282810616SSebastien.Roy@Sun.COM 	    !iptun_in_6to4_ok(iptun, outer4, inner6))
282910616SSebastien.Roy@Sun.COM 		goto drop;
283010616SSebastien.Roy@Sun.COM 
283110616SSebastien.Roy@Sun.COM 	/*
283210616SSebastien.Roy@Sun.COM 	 * We need to statistically account for each packet individually, so
283310616SSebastien.Roy@Sun.COM 	 * we might as well split up any b_next chains here.
283410616SSebastien.Roy@Sun.COM 	 */
283510616SSebastien.Roy@Sun.COM 	do {
2836*11042SErik.Nordmark@Sun.COM 		mblk_t	*mp;
2837*11042SErik.Nordmark@Sun.COM 
283810616SSebastien.Roy@Sun.COM 		mp = data_mp->b_next;
283910616SSebastien.Roy@Sun.COM 		data_mp->b_next = NULL;
284010616SSebastien.Roy@Sun.COM 
284110616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_ipackets);
284210616SSebastien.Roy@Sun.COM 		atomic_add_64(&iptun->iptun_rbytes, msgdsize(data_mp));
284310616SSebastien.Roy@Sun.COM 		mac_rx(iptun->iptun_mh, NULL, data_mp);
284410616SSebastien.Roy@Sun.COM 
284510616SSebastien.Roy@Sun.COM 		data_mp = mp;
284610616SSebastien.Roy@Sun.COM 	} while (data_mp != NULL);
284710616SSebastien.Roy@Sun.COM 	return;
284810616SSebastien.Roy@Sun.COM drop:
2849*11042SErik.Nordmark@Sun.COM 	iptun_drop_pkt(data_mp, &iptun->iptun_ierrors);
285010616SSebastien.Roy@Sun.COM }
285110616SSebastien.Roy@Sun.COM 
285210616SSebastien.Roy@Sun.COM /*
285310616SSebastien.Roy@Sun.COM  * Do 6to4-specific header-processing on output.  Return B_TRUE if the packet
285410616SSebastien.Roy@Sun.COM  * was processed without issue, or B_FALSE if the packet had issues and should
285510616SSebastien.Roy@Sun.COM  * be dropped.
285610616SSebastien.Roy@Sun.COM  */
285710616SSebastien.Roy@Sun.COM static boolean_t
285810616SSebastien.Roy@Sun.COM iptun_out_process_6to4(iptun_t *iptun, ipha_t *outer4, ip6_t *inner6)
285910616SSebastien.Roy@Sun.COM {
286010616SSebastien.Roy@Sun.COM 	ipaddr_t v4addr;
286110616SSebastien.Roy@Sun.COM 
286210616SSebastien.Roy@Sun.COM 	/*
286310616SSebastien.Roy@Sun.COM 	 * IPv6 source must be a 6to4 address.  This is because a conscious
286410616SSebastien.Roy@Sun.COM 	 * decision was made to not allow a Solaris system to be used as a
286510616SSebastien.Roy@Sun.COM 	 * relay router (for security reasons) when 6to4 was initially
286610616SSebastien.Roy@Sun.COM 	 * integrated.  If this decision is ever reversed, the following check
286710616SSebastien.Roy@Sun.COM 	 * can be removed.
286810616SSebastien.Roy@Sun.COM 	 */
286910616SSebastien.Roy@Sun.COM 	if (!IN6_IS_ADDR_6TO4(&inner6->ip6_src))
287010616SSebastien.Roy@Sun.COM 		return (B_FALSE);
287110616SSebastien.Roy@Sun.COM 
287210616SSebastien.Roy@Sun.COM 	/*
287310616SSebastien.Roy@Sun.COM 	 * RFC3056 mandates that the IPv4 source MUST be set to the IPv4
287410616SSebastien.Roy@Sun.COM 	 * portion of the 6to4 IPv6 source address.  In other words, make sure
287510616SSebastien.Roy@Sun.COM 	 * that we're tunneling packets from our own 6to4 site.
287610616SSebastien.Roy@Sun.COM 	 */
287710616SSebastien.Roy@Sun.COM 	IN6_6TO4_TO_V4ADDR(&inner6->ip6_src, (struct in_addr *)&v4addr);
287810616SSebastien.Roy@Sun.COM 	if (outer4->ipha_src != v4addr)
287910616SSebastien.Roy@Sun.COM 		return (B_FALSE);
288010616SSebastien.Roy@Sun.COM 
288110616SSebastien.Roy@Sun.COM 	/*
288210616SSebastien.Roy@Sun.COM 	 * Automatically set the destination of the outer IPv4 header as
288310616SSebastien.Roy@Sun.COM 	 * described in RFC3056.  There are two possibilities:
288410616SSebastien.Roy@Sun.COM 	 *
288510616SSebastien.Roy@Sun.COM 	 * a. If the IPv6 destination is a 6to4 address, set the IPv4 address
288610616SSebastien.Roy@Sun.COM 	 *    to the IPv4 portion of the 6to4 address.
288710616SSebastien.Roy@Sun.COM 	 * b. If the IPv6 destination is a native IPv6 address, set the IPv4
288810616SSebastien.Roy@Sun.COM 	 *    destination to the address of a relay router.
288910616SSebastien.Roy@Sun.COM 	 *
289010616SSebastien.Roy@Sun.COM 	 * Design Note: b shouldn't be necessary here, and this is a flaw in
289110616SSebastien.Roy@Sun.COM 	 * the design of the 6to4relay command.  Instead of setting a 6to4
289210616SSebastien.Roy@Sun.COM 	 * relay address in this module via an ioctl, the 6to4relay command
289310616SSebastien.Roy@Sun.COM 	 * could simply add a IPv6 route for native IPv6 addresses (such as a
289410616SSebastien.Roy@Sun.COM 	 * default route) in the forwarding table that uses a 6to4 destination
289510616SSebastien.Roy@Sun.COM 	 * as its next hop, and the IPv4 portion of that address could be a
289610616SSebastien.Roy@Sun.COM 	 * 6to4 relay address.  In order for this to work, IP would have to
289710616SSebastien.Roy@Sun.COM 	 * resolve the next hop address, which would necessitate a link-layer
289810616SSebastien.Roy@Sun.COM 	 * address resolver for 6to4 links, which doesn't exist today.
289910616SSebastien.Roy@Sun.COM 	 *
290010616SSebastien.Roy@Sun.COM 	 * In fact, if a resolver existed for 6to4 links, then setting the
290110616SSebastien.Roy@Sun.COM 	 * IPv4 destination in the outer header could be done as part of
290210616SSebastien.Roy@Sun.COM 	 * link-layer address resolution and fast-path header generation, and
290310616SSebastien.Roy@Sun.COM 	 * not here.
290410616SSebastien.Roy@Sun.COM 	 */
290510616SSebastien.Roy@Sun.COM 	if (IN6_IS_ADDR_6TO4(&inner6->ip6_dst)) {
290610616SSebastien.Roy@Sun.COM 		/* destination is a 6to4 router */
290710616SSebastien.Roy@Sun.COM 		IN6_6TO4_TO_V4ADDR(&inner6->ip6_dst,
290810616SSebastien.Roy@Sun.COM 		    (struct in_addr *)&outer4->ipha_dst);
2909*11042SErik.Nordmark@Sun.COM 
2910*11042SErik.Nordmark@Sun.COM 		/* Reject attempts to send to INADDR_ANY */
2911*11042SErik.Nordmark@Sun.COM 		if (outer4->ipha_dst == INADDR_ANY)
2912*11042SErik.Nordmark@Sun.COM 			return (B_FALSE);
291310616SSebastien.Roy@Sun.COM 	} else {
291410616SSebastien.Roy@Sun.COM 		/*
291510616SSebastien.Roy@Sun.COM 		 * The destination is a native IPv6 address.  If output to a
291610616SSebastien.Roy@Sun.COM 		 * relay-router is enabled, use the relay-router's IPv4
291710616SSebastien.Roy@Sun.COM 		 * address as the destination.
291810616SSebastien.Roy@Sun.COM 		 */
291910616SSebastien.Roy@Sun.COM 		if (iptun->iptun_iptuns->iptuns_relay_rtr_addr == INADDR_ANY)
292010616SSebastien.Roy@Sun.COM 			return (B_FALSE);
292110616SSebastien.Roy@Sun.COM 		outer4->ipha_dst = iptun->iptun_iptuns->iptuns_relay_rtr_addr;
292210616SSebastien.Roy@Sun.COM 	}
292310616SSebastien.Roy@Sun.COM 
292410616SSebastien.Roy@Sun.COM 	/*
292510616SSebastien.Roy@Sun.COM 	 * If the outer source and destination are equal, this means that the
292610616SSebastien.Roy@Sun.COM 	 * 6to4 router somehow forwarded an IPv6 packet destined for its own
292710616SSebastien.Roy@Sun.COM 	 * 6to4 site to its 6to4 tunnel interface, which will result in this
292810616SSebastien.Roy@Sun.COM 	 * packet infinitely bouncing between ip and iptun.
292910616SSebastien.Roy@Sun.COM 	 */
293010616SSebastien.Roy@Sun.COM 	return (outer4->ipha_src != outer4->ipha_dst);
293110616SSebastien.Roy@Sun.COM }
293210616SSebastien.Roy@Sun.COM 
293310616SSebastien.Roy@Sun.COM /*
293410616SSebastien.Roy@Sun.COM  * Process output packets with outer IPv4 headers.  Frees mp and bumps stat on
293510616SSebastien.Roy@Sun.COM  * error.
293610616SSebastien.Roy@Sun.COM  */
293710616SSebastien.Roy@Sun.COM static mblk_t *
293810616SSebastien.Roy@Sun.COM iptun_out_process_ipv4(iptun_t *iptun, mblk_t *mp, ipha_t *outer4,
2939*11042SErik.Nordmark@Sun.COM     ipha_t *inner4, ip6_t *inner6, ip_xmit_attr_t *ixa)
294010616SSebastien.Roy@Sun.COM {
294110616SSebastien.Roy@Sun.COM 	uint8_t	*innerptr = (inner4 != NULL ?
294210616SSebastien.Roy@Sun.COM 	    (uint8_t *)inner4 : (uint8_t *)inner6);
2943*11042SErik.Nordmark@Sun.COM 	size_t	minmtu = iptun->iptun_typeinfo->iti_minmtu;
294410616SSebastien.Roy@Sun.COM 
294510616SSebastien.Roy@Sun.COM 	if (inner4 != NULL) {
294610616SSebastien.Roy@Sun.COM 		ASSERT(outer4->ipha_protocol == IPPROTO_ENCAP);
294710616SSebastien.Roy@Sun.COM 		/*
294810616SSebastien.Roy@Sun.COM 		 * Copy the tos from the inner IPv4 header. We mask off ECN
294910616SSebastien.Roy@Sun.COM 		 * bits (bits 6 and 7) because there is currently no
295010616SSebastien.Roy@Sun.COM 		 * tunnel-tunnel communication to determine if both sides
295110616SSebastien.Roy@Sun.COM 		 * support ECN.  We opt for the safe choice: don't copy the
295210616SSebastien.Roy@Sun.COM 		 * ECN bits when doing encapsulation.
295310616SSebastien.Roy@Sun.COM 		 */
295410616SSebastien.Roy@Sun.COM 		outer4->ipha_type_of_service =
295510616SSebastien.Roy@Sun.COM 		    inner4->ipha_type_of_service & ~0x03;
295610616SSebastien.Roy@Sun.COM 	} else {
295710616SSebastien.Roy@Sun.COM 		ASSERT(outer4->ipha_protocol == IPPROTO_IPV6 &&
295810616SSebastien.Roy@Sun.COM 		    inner6 != NULL);
295910616SSebastien.Roy@Sun.COM 	}
2960*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_flags & IXAF_PMTU_IPV4_DF)
2961*11042SErik.Nordmark@Sun.COM 		outer4->ipha_fragment_offset_and_flags |= IPH_DF_HTONS;
2962*11042SErik.Nordmark@Sun.COM 	else
2963*11042SErik.Nordmark@Sun.COM 		outer4->ipha_fragment_offset_and_flags &= ~IPH_DF_HTONS;
296410616SSebastien.Roy@Sun.COM 
296510616SSebastien.Roy@Sun.COM 	/*
296610616SSebastien.Roy@Sun.COM 	 * As described in section 3.2.2 of RFC4213, if the packet payload is
296710616SSebastien.Roy@Sun.COM 	 * less than or equal to the minimum MTU size, then we need to allow
296810616SSebastien.Roy@Sun.COM 	 * IPv4 to fragment the packet.  The reason is that even if we end up
296910616SSebastien.Roy@Sun.COM 	 * receiving an ICMP frag-needed, the interface above this tunnel
297010616SSebastien.Roy@Sun.COM 	 * won't be allowed to drop its MTU as a result, since the packet was
297110616SSebastien.Roy@Sun.COM 	 * already smaller than the smallest allowable MTU for that interface.
297210616SSebastien.Roy@Sun.COM 	 */
2973*11042SErik.Nordmark@Sun.COM 	if (mp->b_wptr - innerptr <= minmtu) {
297410616SSebastien.Roy@Sun.COM 		outer4->ipha_fragment_offset_and_flags = 0;
2975*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags &= ~IXAF_DONTFRAG;
2976*11042SErik.Nordmark@Sun.COM 	} else if (!(ixa->ixa_flags & IXAF_PMTU_TOO_SMALL) &&
2977*11042SErik.Nordmark@Sun.COM 	    (iptun->iptun_typeinfo->iti_type != IPTUN_TYPE_6TO4)) {
2978*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags |= IXAF_DONTFRAG;
2979*11042SErik.Nordmark@Sun.COM 	}
2980*11042SErik.Nordmark@Sun.COM 
2981*11042SErik.Nordmark@Sun.COM 	ixa->ixa_ip_hdr_length = IPH_HDR_LENGTH(outer4);
2982*11042SErik.Nordmark@Sun.COM 	ixa->ixa_pktlen = msgdsize(mp);
2983*11042SErik.Nordmark@Sun.COM 	ixa->ixa_protocol = outer4->ipha_protocol;
2984*11042SErik.Nordmark@Sun.COM 
2985*11042SErik.Nordmark@Sun.COM 	outer4->ipha_length = htons(ixa->ixa_pktlen);
298610616SSebastien.Roy@Sun.COM 	return (mp);
298710616SSebastien.Roy@Sun.COM }
298810616SSebastien.Roy@Sun.COM 
298910616SSebastien.Roy@Sun.COM /*
299010616SSebastien.Roy@Sun.COM  * Insert an encapsulation limit destination option in the packet provided.
299110616SSebastien.Roy@Sun.COM  * Always consumes the mp argument and returns a new mblk pointer.
299210616SSebastien.Roy@Sun.COM  */
299310616SSebastien.Roy@Sun.COM static mblk_t *
299410616SSebastien.Roy@Sun.COM iptun_insert_encaplimit(iptun_t *iptun, mblk_t *mp, ip6_t *outer6,
299510616SSebastien.Roy@Sun.COM     uint8_t limit)
299610616SSebastien.Roy@Sun.COM {
299710616SSebastien.Roy@Sun.COM 	mblk_t			*newmp;
299810616SSebastien.Roy@Sun.COM 	iptun_ipv6hdrs_t	*newouter6;
299910616SSebastien.Roy@Sun.COM 
300010616SSebastien.Roy@Sun.COM 	ASSERT(outer6->ip6_nxt == IPPROTO_IPV6);
300110616SSebastien.Roy@Sun.COM 	ASSERT(mp->b_cont == NULL);
300210616SSebastien.Roy@Sun.COM 
300310616SSebastien.Roy@Sun.COM 	mp->b_rptr += sizeof (ip6_t);
3004*11042SErik.Nordmark@Sun.COM 	newmp = allocb(sizeof (iptun_ipv6hdrs_t) + MBLKL(mp), BPRI_MED);
300510616SSebastien.Roy@Sun.COM 	if (newmp == NULL) {
300610616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_noxmtbuf);
300710616SSebastien.Roy@Sun.COM 		return (NULL);
300810616SSebastien.Roy@Sun.COM 	}
300910616SSebastien.Roy@Sun.COM 	newmp->b_wptr += sizeof (iptun_ipv6hdrs_t);
301010616SSebastien.Roy@Sun.COM 	/* Copy the payload (Starting with the inner IPv6 header). */
301110616SSebastien.Roy@Sun.COM 	bcopy(mp->b_rptr, newmp->b_wptr, MBLKL(mp));
301210616SSebastien.Roy@Sun.COM 	newmp->b_wptr += MBLKL(mp);
301310616SSebastien.Roy@Sun.COM 	newouter6 = (iptun_ipv6hdrs_t *)newmp->b_rptr;
301410616SSebastien.Roy@Sun.COM 	/* Now copy the outer IPv6 header. */
301510616SSebastien.Roy@Sun.COM 	bcopy(outer6, &newouter6->it6h_ip6h, sizeof (ip6_t));
301610616SSebastien.Roy@Sun.COM 	newouter6->it6h_ip6h.ip6_nxt = IPPROTO_DSTOPTS;
301710616SSebastien.Roy@Sun.COM 	newouter6->it6h_encaplim = iptun_encaplim_init;
301810616SSebastien.Roy@Sun.COM 	newouter6->it6h_encaplim.iel_destopt.ip6d_nxt = outer6->ip6_nxt;
301910616SSebastien.Roy@Sun.COM 	newouter6->it6h_encaplim.iel_telopt.ip6ot_encap_limit = limit;
302010616SSebastien.Roy@Sun.COM 
302110616SSebastien.Roy@Sun.COM 	/*
302210616SSebastien.Roy@Sun.COM 	 * The payload length will be set at the end of
302310616SSebastien.Roy@Sun.COM 	 * iptun_out_process_ipv6().
302410616SSebastien.Roy@Sun.COM 	 */
302510616SSebastien.Roy@Sun.COM 
302610616SSebastien.Roy@Sun.COM 	freemsg(mp);
302710616SSebastien.Roy@Sun.COM 	return (newmp);
302810616SSebastien.Roy@Sun.COM }
302910616SSebastien.Roy@Sun.COM 
303010616SSebastien.Roy@Sun.COM /*
303110616SSebastien.Roy@Sun.COM  * Process output packets with outer IPv6 headers.  Frees mp and bumps stats
303210616SSebastien.Roy@Sun.COM  * on error.
303310616SSebastien.Roy@Sun.COM  */
303410616SSebastien.Roy@Sun.COM static mblk_t *
3035*11042SErik.Nordmark@Sun.COM iptun_out_process_ipv6(iptun_t *iptun, mblk_t *mp, ip6_t *outer6,
3036*11042SErik.Nordmark@Sun.COM     ipha_t *inner4, ip6_t *inner6, ip_xmit_attr_t *ixa)
303710616SSebastien.Roy@Sun.COM {
3038*11042SErik.Nordmark@Sun.COM 	uint8_t		*innerptr = (inner4 != NULL ?
3039*11042SErik.Nordmark@Sun.COM 	    (uint8_t *)inner4 : (uint8_t *)inner6);
3040*11042SErik.Nordmark@Sun.COM 	size_t		minmtu = iptun->iptun_typeinfo->iti_minmtu;
304110616SSebastien.Roy@Sun.COM 	uint8_t		*limit, *configlimit;
304210616SSebastien.Roy@Sun.COM 	uint32_t	offset;
304310616SSebastien.Roy@Sun.COM 	iptun_ipv6hdrs_t *v6hdrs;
304410616SSebastien.Roy@Sun.COM 
304510616SSebastien.Roy@Sun.COM 	if (inner6 != NULL && iptun_find_encaplimit(mp, inner6, &limit)) {
304610616SSebastien.Roy@Sun.COM 		/*
304710616SSebastien.Roy@Sun.COM 		 * The inner packet is an IPv6 packet which itself contains an
304810616SSebastien.Roy@Sun.COM 		 * encapsulation limit option.  The limit variable points to
304910616SSebastien.Roy@Sun.COM 		 * the value in the embedded option.  Process the
305010616SSebastien.Roy@Sun.COM 		 * encapsulation limit option as specified in RFC 2473.
305110616SSebastien.Roy@Sun.COM 		 *
305210616SSebastien.Roy@Sun.COM 		 * If limit is 0, then we've exceeded the limit and we need to
305310616SSebastien.Roy@Sun.COM 		 * send back an ICMPv6 parameter problem message.
305410616SSebastien.Roy@Sun.COM 		 *
305510616SSebastien.Roy@Sun.COM 		 * If limit is > 0, then we decrement it by 1 and make sure
305610616SSebastien.Roy@Sun.COM 		 * that the encapsulation limit option in the outer header
305710616SSebastien.Roy@Sun.COM 		 * reflects that (adding an option if one isn't already
305810616SSebastien.Roy@Sun.COM 		 * there).
305910616SSebastien.Roy@Sun.COM 		 */
306010616SSebastien.Roy@Sun.COM 		ASSERT(limit > mp->b_rptr && limit < mp->b_wptr);
306110616SSebastien.Roy@Sun.COM 		if (*limit == 0) {
306210616SSebastien.Roy@Sun.COM 			mp->b_rptr = (uint8_t *)inner6;
306310616SSebastien.Roy@Sun.COM 			offset = limit - mp->b_rptr;
306410616SSebastien.Roy@Sun.COM 			iptun_icmp_error_v6(iptun, inner6, mp, ICMP6_PARAM_PROB,
3065*11042SErik.Nordmark@Sun.COM 			    0, offset, ixa->ixa_tsl);
306610616SSebastien.Roy@Sun.COM 			atomic_inc_64(&iptun->iptun_noxmtbuf);
306710616SSebastien.Roy@Sun.COM 			return (NULL);
306810616SSebastien.Roy@Sun.COM 		}
306910616SSebastien.Roy@Sun.COM 
307010616SSebastien.Roy@Sun.COM 		/*
307110616SSebastien.Roy@Sun.COM 		 * The outer header requires an encapsulation limit option.
307210616SSebastien.Roy@Sun.COM 		 * If there isn't one already, add one.
307310616SSebastien.Roy@Sun.COM 		 */
307410616SSebastien.Roy@Sun.COM 		if (iptun->iptun_encaplimit == 0) {
307510616SSebastien.Roy@Sun.COM 			if ((mp = iptun_insert_encaplimit(iptun, mp, outer6,
307610616SSebastien.Roy@Sun.COM 			    (*limit - 1))) == NULL)
307710616SSebastien.Roy@Sun.COM 				return (NULL);
3078*11042SErik.Nordmark@Sun.COM 			v6hdrs = (iptun_ipv6hdrs_t *)mp->b_rptr;
307910616SSebastien.Roy@Sun.COM 		} else {
308010616SSebastien.Roy@Sun.COM 			/*
308110616SSebastien.Roy@Sun.COM 			 * There is an existing encapsulation limit option in
308210616SSebastien.Roy@Sun.COM 			 * the outer header.  If the inner encapsulation limit
308310616SSebastien.Roy@Sun.COM 			 * is less than the configured encapsulation limit,
308410616SSebastien.Roy@Sun.COM 			 * update the outer encapsulation limit to reflect
308510616SSebastien.Roy@Sun.COM 			 * this lesser value.
308610616SSebastien.Roy@Sun.COM 			 */
308710616SSebastien.Roy@Sun.COM 			v6hdrs = (iptun_ipv6hdrs_t *)mp->b_rptr;
308810616SSebastien.Roy@Sun.COM 			configlimit =
308910616SSebastien.Roy@Sun.COM 			    &v6hdrs->it6h_encaplim.iel_telopt.ip6ot_encap_limit;
309010616SSebastien.Roy@Sun.COM 			if ((*limit - 1) < *configlimit)
309110616SSebastien.Roy@Sun.COM 				*configlimit = (*limit - 1);
309210616SSebastien.Roy@Sun.COM 		}
3093*11042SErik.Nordmark@Sun.COM 		ixa->ixa_ip_hdr_length = sizeof (iptun_ipv6hdrs_t);
3094*11042SErik.Nordmark@Sun.COM 		ixa->ixa_protocol = v6hdrs->it6h_encaplim.iel_destopt.ip6d_nxt;
3095*11042SErik.Nordmark@Sun.COM 	} else {
3096*11042SErik.Nordmark@Sun.COM 		ixa->ixa_ip_hdr_length = sizeof (ip6_t);
3097*11042SErik.Nordmark@Sun.COM 		ixa->ixa_protocol = outer6->ip6_nxt;
309810616SSebastien.Roy@Sun.COM 	}
3099*11042SErik.Nordmark@Sun.COM 	/*
3100*11042SErik.Nordmark@Sun.COM 	 * See iptun_output_process_ipv4() why we allow fragmentation for
3101*11042SErik.Nordmark@Sun.COM 	 * small packets
3102*11042SErik.Nordmark@Sun.COM 	 */
3103*11042SErik.Nordmark@Sun.COM 	if (mp->b_wptr - innerptr <= minmtu)
3104*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags &= ~IXAF_DONTFRAG;
3105*11042SErik.Nordmark@Sun.COM 	else if (!(ixa->ixa_flags & IXAF_PMTU_TOO_SMALL))
3106*11042SErik.Nordmark@Sun.COM 		ixa->ixa_flags |= IXAF_DONTFRAG;
3107*11042SErik.Nordmark@Sun.COM 
3108*11042SErik.Nordmark@Sun.COM 	ixa->ixa_pktlen = msgdsize(mp);
3109*11042SErik.Nordmark@Sun.COM 	outer6->ip6_plen = htons(ixa->ixa_pktlen - sizeof (ip6_t));
311010616SSebastien.Roy@Sun.COM 	return (mp);
311110616SSebastien.Roy@Sun.COM }
311210616SSebastien.Roy@Sun.COM 
311310616SSebastien.Roy@Sun.COM /*
311410616SSebastien.Roy@Sun.COM  * The IP tunneling MAC-type plugins have already done most of the header
311510616SSebastien.Roy@Sun.COM  * processing and validity checks.  We are simply responsible for multiplexing
311610616SSebastien.Roy@Sun.COM  * down to the ip module below us.
311710616SSebastien.Roy@Sun.COM  */
311810616SSebastien.Roy@Sun.COM static void
311910616SSebastien.Roy@Sun.COM iptun_output(iptun_t *iptun, mblk_t *mp)
312010616SSebastien.Roy@Sun.COM {
312110616SSebastien.Roy@Sun.COM 	conn_t	*connp = iptun->iptun_connp;
312210616SSebastien.Roy@Sun.COM 	mblk_t	*newmp;
3123*11042SErik.Nordmark@Sun.COM 	int	error;
3124*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t	*ixa;
312510616SSebastien.Roy@Sun.COM 
312610616SSebastien.Roy@Sun.COM 	ASSERT(mp->b_datap->db_type == M_DATA);
312710616SSebastien.Roy@Sun.COM 
312810616SSebastien.Roy@Sun.COM 	if (mp->b_cont != NULL) {
312910616SSebastien.Roy@Sun.COM 		if ((newmp = msgpullup(mp, -1)) == NULL) {
313010616SSebastien.Roy@Sun.COM 			iptun_drop_pkt(mp, &iptun->iptun_noxmtbuf);
313110616SSebastien.Roy@Sun.COM 			return;
313210616SSebastien.Roy@Sun.COM 		}
313310616SSebastien.Roy@Sun.COM 		freemsg(mp);
313410616SSebastien.Roy@Sun.COM 		mp = newmp;
313510616SSebastien.Roy@Sun.COM 	}
313610616SSebastien.Roy@Sun.COM 
3137*11042SErik.Nordmark@Sun.COM 	if (iptun->iptun_typeinfo->iti_type == IPTUN_TYPE_6TO4) {
3138*11042SErik.Nordmark@Sun.COM 		iptun_output_6to4(iptun, mp);
3139*11042SErik.Nordmark@Sun.COM 		return;
3140*11042SErik.Nordmark@Sun.COM 	}
3141*11042SErik.Nordmark@Sun.COM 
3142*11042SErik.Nordmark@Sun.COM 	if (is_system_labeled()) {
3143*11042SErik.Nordmark@Sun.COM 		/*
3144*11042SErik.Nordmark@Sun.COM 		 * Since the label can be different meaning a potentially
3145*11042SErik.Nordmark@Sun.COM 		 * different IRE,we always use a unique ip_xmit_attr_t.
3146*11042SErik.Nordmark@Sun.COM 		 */
3147*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa_exclusive(connp);
3148*11042SErik.Nordmark@Sun.COM 	} else {
3149*11042SErik.Nordmark@Sun.COM 		/*
3150*11042SErik.Nordmark@Sun.COM 		 * If no other thread is using conn_ixa this just gets a
3151*11042SErik.Nordmark@Sun.COM 		 * reference to conn_ixa. Otherwise we get a safe copy of
3152*11042SErik.Nordmark@Sun.COM 		 * conn_ixa.
3153*11042SErik.Nordmark@Sun.COM 		 */
3154*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa(connp, B_FALSE);
3155*11042SErik.Nordmark@Sun.COM 	}
3156*11042SErik.Nordmark@Sun.COM 	if (ixa == NULL) {
3157*11042SErik.Nordmark@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3158*11042SErik.Nordmark@Sun.COM 		return;
3159*11042SErik.Nordmark@Sun.COM 	}
3160*11042SErik.Nordmark@Sun.COM 
3161*11042SErik.Nordmark@Sun.COM 	/*
3162*11042SErik.Nordmark@Sun.COM 	 * In case we got a safe copy of conn_ixa, then we need
3163*11042SErik.Nordmark@Sun.COM 	 * to fill in any pointers in it.
3164*11042SErik.Nordmark@Sun.COM 	 */
3165*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_ire == NULL) {
3166*11042SErik.Nordmark@Sun.COM 		error = ip_attr_connect(connp, ixa, &connp->conn_saddr_v6,
3167*11042SErik.Nordmark@Sun.COM 		    &connp->conn_faddr_v6, &connp->conn_faddr_v6, 0,
3168*11042SErik.Nordmark@Sun.COM 		    NULL, NULL, 0);
3169*11042SErik.Nordmark@Sun.COM 		if (error != 0) {
3170*11042SErik.Nordmark@Sun.COM 			if (ixa->ixa_ire != NULL &&
3171*11042SErik.Nordmark@Sun.COM 			    (error == EHOSTUNREACH || error == ENETUNREACH)) {
3172*11042SErik.Nordmark@Sun.COM 				/*
3173*11042SErik.Nordmark@Sun.COM 				 * Let conn_ip_output/ire_send_noroute return
3174*11042SErik.Nordmark@Sun.COM 				 * the error and send any local ICMP error.
3175*11042SErik.Nordmark@Sun.COM 				 */
3176*11042SErik.Nordmark@Sun.COM 				error = 0;
3177*11042SErik.Nordmark@Sun.COM 			} else {
3178*11042SErik.Nordmark@Sun.COM 				ixa_refrele(ixa);
3179*11042SErik.Nordmark@Sun.COM 				iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3180*11042SErik.Nordmark@Sun.COM 				return;
3181*11042SErik.Nordmark@Sun.COM 			}
3182*11042SErik.Nordmark@Sun.COM 		}
3183*11042SErik.Nordmark@Sun.COM 	}
3184*11042SErik.Nordmark@Sun.COM 
3185*11042SErik.Nordmark@Sun.COM 	iptun_output_common(iptun, ixa, mp);
3186*11042SErik.Nordmark@Sun.COM 	ixa_refrele(ixa);
3187*11042SErik.Nordmark@Sun.COM }
3188*11042SErik.Nordmark@Sun.COM 
3189*11042SErik.Nordmark@Sun.COM /*
3190*11042SErik.Nordmark@Sun.COM  * We use an ixa based on the last destination.
3191*11042SErik.Nordmark@Sun.COM  */
3192*11042SErik.Nordmark@Sun.COM static void
3193*11042SErik.Nordmark@Sun.COM iptun_output_6to4(iptun_t *iptun, mblk_t *mp)
3194*11042SErik.Nordmark@Sun.COM {
3195*11042SErik.Nordmark@Sun.COM 	conn_t		*connp = iptun->iptun_connp;
3196*11042SErik.Nordmark@Sun.COM 	ipha_t		*outer4, *inner4;
3197*11042SErik.Nordmark@Sun.COM 	ip6_t		*outer6, *inner6;
3198*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t	*ixa;
3199*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_t	*oldixa;
3200*11042SErik.Nordmark@Sun.COM 	int		error;
3201*11042SErik.Nordmark@Sun.COM 	boolean_t	need_connect;
3202*11042SErik.Nordmark@Sun.COM 	in6_addr_t	v6dst;
3203*11042SErik.Nordmark@Sun.COM 
3204*11042SErik.Nordmark@Sun.COM 	ASSERT(mp->b_cont == NULL);	/* Verified by iptun_output */
3205*11042SErik.Nordmark@Sun.COM 
3206*11042SErik.Nordmark@Sun.COM 	/* Make sure we set ipha_dst before we look at ipha_dst */
3207*11042SErik.Nordmark@Sun.COM 
3208*11042SErik.Nordmark@Sun.COM 	(void) iptun_find_headers(mp, 0, &outer4, &inner4, &outer6, &inner6);
3209*11042SErik.Nordmark@Sun.COM 	ASSERT(outer4 != NULL);
3210*11042SErik.Nordmark@Sun.COM 	if (!iptun_out_process_6to4(iptun, outer4, inner6)) {
3211*11042SErik.Nordmark@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3212*11042SErik.Nordmark@Sun.COM 		return;
3213*11042SErik.Nordmark@Sun.COM 	}
3214*11042SErik.Nordmark@Sun.COM 
3215*11042SErik.Nordmark@Sun.COM 	if (is_system_labeled()) {
3216*11042SErik.Nordmark@Sun.COM 		/*
3217*11042SErik.Nordmark@Sun.COM 		 * Since the label can be different meaning a potentially
3218*11042SErik.Nordmark@Sun.COM 		 * different IRE,we always use a unique ip_xmit_attr_t.
3219*11042SErik.Nordmark@Sun.COM 		 */
3220*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa_exclusive(connp);
3221*11042SErik.Nordmark@Sun.COM 	} else {
3222*11042SErik.Nordmark@Sun.COM 		/*
3223*11042SErik.Nordmark@Sun.COM 		 * If no other thread is using conn_ixa this just gets a
3224*11042SErik.Nordmark@Sun.COM 		 * reference to conn_ixa. Otherwise we get a safe copy of
3225*11042SErik.Nordmark@Sun.COM 		 * conn_ixa.
3226*11042SErik.Nordmark@Sun.COM 		 */
3227*11042SErik.Nordmark@Sun.COM 		ixa = conn_get_ixa(connp, B_FALSE);
3228*11042SErik.Nordmark@Sun.COM 	}
3229*11042SErik.Nordmark@Sun.COM 	if (ixa == NULL) {
3230*11042SErik.Nordmark@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3231*11042SErik.Nordmark@Sun.COM 		return;
3232*11042SErik.Nordmark@Sun.COM 	}
3233*11042SErik.Nordmark@Sun.COM 
3234*11042SErik.Nordmark@Sun.COM 	mutex_enter(&connp->conn_lock);
3235*11042SErik.Nordmark@Sun.COM 	if (connp->conn_v4lastdst == outer4->ipha_dst) {
3236*11042SErik.Nordmark@Sun.COM 		need_connect = (ixa->ixa_ire == NULL);
3237*11042SErik.Nordmark@Sun.COM 	} else {
3238*11042SErik.Nordmark@Sun.COM 		/* In case previous destination was multirt */
3239*11042SErik.Nordmark@Sun.COM 		ip_attr_newdst(ixa);
3240*11042SErik.Nordmark@Sun.COM 
3241*11042SErik.Nordmark@Sun.COM 		/*
3242*11042SErik.Nordmark@Sun.COM 		 * We later update conn_ixa when we update conn_v4lastdst
3243*11042SErik.Nordmark@Sun.COM 		 * which enables subsequent packets to avoid redoing
3244*11042SErik.Nordmark@Sun.COM 		 * ip_attr_connect
3245*11042SErik.Nordmark@Sun.COM 		 */
3246*11042SErik.Nordmark@Sun.COM 		need_connect = B_TRUE;
3247*11042SErik.Nordmark@Sun.COM 	}
3248*11042SErik.Nordmark@Sun.COM 	mutex_exit(&connp->conn_lock);
3249*11042SErik.Nordmark@Sun.COM 
3250*11042SErik.Nordmark@Sun.COM 	/*
3251*11042SErik.Nordmark@Sun.COM 	 * In case we got a safe copy of conn_ixa, or otherwise we don't
3252*11042SErik.Nordmark@Sun.COM 	 * have a current ixa_ire, then we need to fill in any pointers in
3253*11042SErik.Nordmark@Sun.COM 	 * the ixa.
3254*11042SErik.Nordmark@Sun.COM 	 */
3255*11042SErik.Nordmark@Sun.COM 	if (need_connect) {
3256*11042SErik.Nordmark@Sun.COM 		IN6_IPADDR_TO_V4MAPPED(outer4->ipha_dst, &v6dst);
3257*11042SErik.Nordmark@Sun.COM 
3258*11042SErik.Nordmark@Sun.COM 		/* We handle IPsec in iptun_output_common */
3259*11042SErik.Nordmark@Sun.COM 		error = ip_attr_connect(connp, ixa, &connp->conn_saddr_v6,
3260*11042SErik.Nordmark@Sun.COM 		    &v6dst, &v6dst, 0, NULL, NULL, 0);
3261*11042SErik.Nordmark@Sun.COM 		if (error != 0) {
3262*11042SErik.Nordmark@Sun.COM 			if (ixa->ixa_ire != NULL &&
3263*11042SErik.Nordmark@Sun.COM 			    (error == EHOSTUNREACH || error == ENETUNREACH)) {
3264*11042SErik.Nordmark@Sun.COM 				/*
3265*11042SErik.Nordmark@Sun.COM 				 * Let conn_ip_output/ire_send_noroute return
3266*11042SErik.Nordmark@Sun.COM 				 * the error and send any local ICMP error.
3267*11042SErik.Nordmark@Sun.COM 				 */
3268*11042SErik.Nordmark@Sun.COM 				error = 0;
3269*11042SErik.Nordmark@Sun.COM 			} else {
3270*11042SErik.Nordmark@Sun.COM 				ixa_refrele(ixa);
3271*11042SErik.Nordmark@Sun.COM 				iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3272*11042SErik.Nordmark@Sun.COM 				return;
3273*11042SErik.Nordmark@Sun.COM 			}
3274*11042SErik.Nordmark@Sun.COM 		}
3275*11042SErik.Nordmark@Sun.COM 	}
3276*11042SErik.Nordmark@Sun.COM 
3277*11042SErik.Nordmark@Sun.COM 	iptun_output_common(iptun, ixa, mp);
3278*11042SErik.Nordmark@Sun.COM 
3279*11042SErik.Nordmark@Sun.COM 	/* Atomically replace conn_ixa and conn_v4lastdst */
3280*11042SErik.Nordmark@Sun.COM 	mutex_enter(&connp->conn_lock);
3281*11042SErik.Nordmark@Sun.COM 	if (connp->conn_v4lastdst != outer4->ipha_dst) {
3282*11042SErik.Nordmark@Sun.COM 		/* Remember the dst which corresponds to conn_ixa */
3283*11042SErik.Nordmark@Sun.COM 		connp->conn_v6lastdst = v6dst;
3284*11042SErik.Nordmark@Sun.COM 		oldixa = conn_replace_ixa(connp, ixa);
3285*11042SErik.Nordmark@Sun.COM 	} else {
3286*11042SErik.Nordmark@Sun.COM 		oldixa = NULL;
3287*11042SErik.Nordmark@Sun.COM 	}
3288*11042SErik.Nordmark@Sun.COM 	mutex_exit(&connp->conn_lock);
3289*11042SErik.Nordmark@Sun.COM 	ixa_refrele(ixa);
3290*11042SErik.Nordmark@Sun.COM 	if (oldixa != NULL)
3291*11042SErik.Nordmark@Sun.COM 		ixa_refrele(oldixa);
3292*11042SErik.Nordmark@Sun.COM }
3293*11042SErik.Nordmark@Sun.COM 
3294*11042SErik.Nordmark@Sun.COM /*
3295*11042SErik.Nordmark@Sun.COM  * Check the destination/label. Modifies *mpp by adding/removing CIPSO.
3296*11042SErik.Nordmark@Sun.COM  *
3297*11042SErik.Nordmark@Sun.COM  * We get the label from the message in order to honor the
3298*11042SErik.Nordmark@Sun.COM  * ULPs/IPs choice of label. This will be NULL for forwarded
3299*11042SErik.Nordmark@Sun.COM  * packets, neighbor discovery packets and some others.
3300*11042SErik.Nordmark@Sun.COM  */
3301*11042SErik.Nordmark@Sun.COM static int
3302*11042SErik.Nordmark@Sun.COM iptun_output_check_label(mblk_t **mpp, ip_xmit_attr_t *ixa)
3303*11042SErik.Nordmark@Sun.COM {
3304*11042SErik.Nordmark@Sun.COM 	cred_t	*cr;
3305*11042SErik.Nordmark@Sun.COM 	int	adjust;
3306*11042SErik.Nordmark@Sun.COM 	int	iplen;
3307*11042SErik.Nordmark@Sun.COM 	int	err;
3308*11042SErik.Nordmark@Sun.COM 	ts_label_t *effective_tsl = NULL;
3309*11042SErik.Nordmark@Sun.COM 
3310*11042SErik.Nordmark@Sun.COM 
3311*11042SErik.Nordmark@Sun.COM 	ASSERT(is_system_labeled());
3312*11042SErik.Nordmark@Sun.COM 
3313*11042SErik.Nordmark@Sun.COM 	cr = msg_getcred(*mpp, NULL);
3314*11042SErik.Nordmark@Sun.COM 	if (cr == NULL)
3315*11042SErik.Nordmark@Sun.COM 		return (0);
3316*11042SErik.Nordmark@Sun.COM 
3317*11042SErik.Nordmark@Sun.COM 	/*
3318*11042SErik.Nordmark@Sun.COM 	 * We need to start with a label based on the IP/ULP above us
3319*11042SErik.Nordmark@Sun.COM 	 */
3320*11042SErik.Nordmark@Sun.COM 	ip_xmit_attr_restore_tsl(ixa, cr);
3321*11042SErik.Nordmark@Sun.COM 
3322*11042SErik.Nordmark@Sun.COM 	/*
3323*11042SErik.Nordmark@Sun.COM 	 * Need to update packet with any CIPSO option since
3324*11042SErik.Nordmark@Sun.COM 	 * conn_ip_output doesn't do that.
3325*11042SErik.Nordmark@Sun.COM 	 */
3326*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_flags & IXAF_IS_IPV4) {
3327*11042SErik.Nordmark@Sun.COM 		ipha_t *ipha;
3328*11042SErik.Nordmark@Sun.COM 
3329*11042SErik.Nordmark@Sun.COM 		ipha = (ipha_t *)(*mpp)->b_rptr;
3330*11042SErik.Nordmark@Sun.COM 		iplen = ntohs(ipha->ipha_length);
3331*11042SErik.Nordmark@Sun.COM 		err = tsol_check_label_v4(ixa->ixa_tsl,
3332*11042SErik.Nordmark@Sun.COM 		    ixa->ixa_zoneid, mpp, CONN_MAC_DEFAULT, B_FALSE,
3333*11042SErik.Nordmark@Sun.COM 		    ixa->ixa_ipst, &effective_tsl);
3334*11042SErik.Nordmark@Sun.COM 		if (err != 0)
3335*11042SErik.Nordmark@Sun.COM 			return (err);
3336*11042SErik.Nordmark@Sun.COM 
3337*11042SErik.Nordmark@Sun.COM 		ipha = (ipha_t *)(*mpp)->b_rptr;
3338*11042SErik.Nordmark@Sun.COM 		adjust = (int)ntohs(ipha->ipha_length) - iplen;
3339*11042SErik.Nordmark@Sun.COM 	} else {
3340*11042SErik.Nordmark@Sun.COM 		ip6_t *ip6h;
3341*11042SErik.Nordmark@Sun.COM 
3342*11042SErik.Nordmark@Sun.COM 		ip6h = (ip6_t *)(*mpp)->b_rptr;
3343*11042SErik.Nordmark@Sun.COM 		iplen = ntohs(ip6h->ip6_plen);
3344*11042SErik.Nordmark@Sun.COM 
3345*11042SErik.Nordmark@Sun.COM 		err = tsol_check_label_v6(ixa->ixa_tsl,
3346*11042SErik.Nordmark@Sun.COM 		    ixa->ixa_zoneid, mpp, CONN_MAC_DEFAULT, B_FALSE,
3347*11042SErik.Nordmark@Sun.COM 		    ixa->ixa_ipst, &effective_tsl);
3348*11042SErik.Nordmark@Sun.COM 		if (err != 0)
3349*11042SErik.Nordmark@Sun.COM 			return (err);
3350*11042SErik.Nordmark@Sun.COM 
3351*11042SErik.Nordmark@Sun.COM 		ip6h = (ip6_t *)(*mpp)->b_rptr;
3352*11042SErik.Nordmark@Sun.COM 		adjust = (int)ntohs(ip6h->ip6_plen) - iplen;
3353*11042SErik.Nordmark@Sun.COM 	}
3354*11042SErik.Nordmark@Sun.COM 
3355*11042SErik.Nordmark@Sun.COM 	if (effective_tsl != NULL) {
3356*11042SErik.Nordmark@Sun.COM 		/* Update the label */
3357*11042SErik.Nordmark@Sun.COM 		ip_xmit_attr_replace_tsl(ixa, effective_tsl);
3358*11042SErik.Nordmark@Sun.COM 	}
3359*11042SErik.Nordmark@Sun.COM 	ixa->ixa_pktlen += adjust;
3360*11042SErik.Nordmark@Sun.COM 	ixa->ixa_ip_hdr_length += adjust;
3361*11042SErik.Nordmark@Sun.COM 	return (0);
3362*11042SErik.Nordmark@Sun.COM }
3363*11042SErik.Nordmark@Sun.COM 
3364*11042SErik.Nordmark@Sun.COM 
3365*11042SErik.Nordmark@Sun.COM static void
3366*11042SErik.Nordmark@Sun.COM iptun_output_common(iptun_t *iptun, ip_xmit_attr_t *ixa, mblk_t *mp)
3367*11042SErik.Nordmark@Sun.COM {
3368*11042SErik.Nordmark@Sun.COM 	ipsec_tun_pol_t	*itp = iptun->iptun_itp;
3369*11042SErik.Nordmark@Sun.COM 	int		outer_hlen;
3370*11042SErik.Nordmark@Sun.COM 	mblk_t		*newmp;
3371*11042SErik.Nordmark@Sun.COM 	ipha_t		*outer4, *inner4;
3372*11042SErik.Nordmark@Sun.COM 	ip6_t		*outer6, *inner6;
3373*11042SErik.Nordmark@Sun.COM 	int		error;
3374*11042SErik.Nordmark@Sun.COM 	boolean_t	update_pktlen;
3375*11042SErik.Nordmark@Sun.COM 
3376*11042SErik.Nordmark@Sun.COM 	ASSERT(ixa->ixa_ire != NULL);
3377*11042SErik.Nordmark@Sun.COM 
3378*11042SErik.Nordmark@Sun.COM 	outer_hlen = iptun_find_headers(mp, 0, &outer4, &inner4, &outer6,
3379*11042SErik.Nordmark@Sun.COM 	    &inner6);
338010616SSebastien.Roy@Sun.COM 	if (outer_hlen == 0) {
338110616SSebastien.Roy@Sun.COM 		iptun_drop_pkt(mp, &iptun->iptun_oerrors);
338210616SSebastien.Roy@Sun.COM 		return;
338310616SSebastien.Roy@Sun.COM 	}
338410616SSebastien.Roy@Sun.COM 
338510616SSebastien.Roy@Sun.COM 	/* Perform header processing. */
3386*11042SErik.Nordmark@Sun.COM 	if (outer4 != NULL) {
3387*11042SErik.Nordmark@Sun.COM 		mp = iptun_out_process_ipv4(iptun, mp, outer4, inner4, inner6,
3388*11042SErik.Nordmark@Sun.COM 		    ixa);
3389*11042SErik.Nordmark@Sun.COM 	} else {
3390*11042SErik.Nordmark@Sun.COM 		mp = iptun_out_process_ipv6(iptun, mp, outer6, inner4, inner6,
3391*11042SErik.Nordmark@Sun.COM 		    ixa);
3392*11042SErik.Nordmark@Sun.COM 	}
339310616SSebastien.Roy@Sun.COM 	if (mp == NULL)
339410616SSebastien.Roy@Sun.COM 		return;
339510616SSebastien.Roy@Sun.COM 
339610616SSebastien.Roy@Sun.COM 	/*
339710616SSebastien.Roy@Sun.COM 	 * Let's hope the compiler optimizes this with "branch taken".
339810616SSebastien.Roy@Sun.COM 	 */
339910616SSebastien.Roy@Sun.COM 	if (itp != NULL && (itp->itp_flags & ITPF_P_ACTIVE)) {
3400*11042SErik.Nordmark@Sun.COM 		/* This updates the ip_xmit_attr_t */
3401*11042SErik.Nordmark@Sun.COM 		mp = ipsec_tun_outbound(mp, iptun, inner4, inner6, outer4,
3402*11042SErik.Nordmark@Sun.COM 		    outer6, outer_hlen, ixa);
3403*11042SErik.Nordmark@Sun.COM 		if (mp == NULL) {
340410616SSebastien.Roy@Sun.COM 			atomic_inc_64(&iptun->iptun_oerrors);
340510616SSebastien.Roy@Sun.COM 			return;
340610616SSebastien.Roy@Sun.COM 		}
3407*11042SErik.Nordmark@Sun.COM 		if (is_system_labeled()) {
3408*11042SErik.Nordmark@Sun.COM 			/*
3409*11042SErik.Nordmark@Sun.COM 			 * Might change the packet by adding/removing CIPSO.
3410*11042SErik.Nordmark@Sun.COM 			 * After this caller inner* and outer* and outer_hlen
3411*11042SErik.Nordmark@Sun.COM 			 * might be invalid.
3412*11042SErik.Nordmark@Sun.COM 			 */
3413*11042SErik.Nordmark@Sun.COM 			error = iptun_output_check_label(&mp, ixa);
3414*11042SErik.Nordmark@Sun.COM 			if (error != 0) {
3415*11042SErik.Nordmark@Sun.COM 				ip2dbg(("label check failed (%d)\n", error));
3416*11042SErik.Nordmark@Sun.COM 				iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3417*11042SErik.Nordmark@Sun.COM 				return;
3418*11042SErik.Nordmark@Sun.COM 			}
3419*11042SErik.Nordmark@Sun.COM 		}
3420*11042SErik.Nordmark@Sun.COM 
342110616SSebastien.Roy@Sun.COM 		/*
342210616SSebastien.Roy@Sun.COM 		 * ipsec_tun_outbound() returns a chain of tunneled IP
342310616SSebastien.Roy@Sun.COM 		 * fragments linked with b_next (or a single message if the
3424*11042SErik.Nordmark@Sun.COM 		 * tunneled packet wasn't a fragment).
3425*11042SErik.Nordmark@Sun.COM 		 * If fragcache returned a list then we need to update
3426*11042SErik.Nordmark@Sun.COM 		 * ixa_pktlen for all packets in the list.
3427*11042SErik.Nordmark@Sun.COM 		 */
3428*11042SErik.Nordmark@Sun.COM 		update_pktlen = (mp->b_next != NULL);
3429*11042SErik.Nordmark@Sun.COM 
3430*11042SErik.Nordmark@Sun.COM 		/*
3431*11042SErik.Nordmark@Sun.COM 		 * Otherwise, we're good to go.  The ixa has been updated with
343210616SSebastien.Roy@Sun.COM 		 * instructions for outbound IPsec processing.
343310616SSebastien.Roy@Sun.COM 		 */
343410616SSebastien.Roy@Sun.COM 		for (newmp = mp; newmp != NULL; newmp = mp) {
343510616SSebastien.Roy@Sun.COM 			atomic_inc_64(&iptun->iptun_opackets);
3436*11042SErik.Nordmark@Sun.COM 			atomic_add_64(&iptun->iptun_obytes, ixa->ixa_pktlen);
343710616SSebastien.Roy@Sun.COM 			mp = mp->b_next;
343810616SSebastien.Roy@Sun.COM 			newmp->b_next = NULL;
3439*11042SErik.Nordmark@Sun.COM 
3440*11042SErik.Nordmark@Sun.COM 			if (update_pktlen)
3441*11042SErik.Nordmark@Sun.COM 				ixa->ixa_pktlen = msgdsize(mp);
3442*11042SErik.Nordmark@Sun.COM 
3443*11042SErik.Nordmark@Sun.COM 			atomic_inc_64(&iptun->iptun_opackets);
3444*11042SErik.Nordmark@Sun.COM 			atomic_add_64(&iptun->iptun_obytes, ixa->ixa_pktlen);
3445*11042SErik.Nordmark@Sun.COM 
3446*11042SErik.Nordmark@Sun.COM 			error = conn_ip_output(newmp, ixa);
3447*11042SErik.Nordmark@Sun.COM 			if (error == EMSGSIZE) {
3448*11042SErik.Nordmark@Sun.COM 				/* IPsec policy might have changed */
3449*11042SErik.Nordmark@Sun.COM 				(void) iptun_update_mtu(iptun, ixa, 0);
3450*11042SErik.Nordmark@Sun.COM 			}
345110616SSebastien.Roy@Sun.COM 		}
345210616SSebastien.Roy@Sun.COM 	} else {
345310616SSebastien.Roy@Sun.COM 		/*
345410616SSebastien.Roy@Sun.COM 		 * The ip module will potentially apply global policy to the
345510616SSebastien.Roy@Sun.COM 		 * packet in its output path if there's no active tunnel
345610616SSebastien.Roy@Sun.COM 		 * policy.
345710616SSebastien.Roy@Sun.COM 		 */
3458*11042SErik.Nordmark@Sun.COM 		ASSERT(ixa->ixa_ipsec_policy == NULL);
3459*11042SErik.Nordmark@Sun.COM 		mp = ip_output_attach_policy(mp, outer4, outer6, NULL, ixa);
3460*11042SErik.Nordmark@Sun.COM 		if (mp == NULL) {
3461*11042SErik.Nordmark@Sun.COM 			atomic_inc_64(&iptun->iptun_oerrors);
3462*11042SErik.Nordmark@Sun.COM 			return;
3463*11042SErik.Nordmark@Sun.COM 		}
3464*11042SErik.Nordmark@Sun.COM 		if (is_system_labeled()) {
3465*11042SErik.Nordmark@Sun.COM 			/*
3466*11042SErik.Nordmark@Sun.COM 			 * Might change the packet by adding/removing CIPSO.
3467*11042SErik.Nordmark@Sun.COM 			 * After this caller inner* and outer* and outer_hlen
3468*11042SErik.Nordmark@Sun.COM 			 * might be invalid.
3469*11042SErik.Nordmark@Sun.COM 			 */
3470*11042SErik.Nordmark@Sun.COM 			error = iptun_output_check_label(&mp, ixa);
3471*11042SErik.Nordmark@Sun.COM 			if (error != 0) {
3472*11042SErik.Nordmark@Sun.COM 				ip2dbg(("label check failed (%d)\n", error));
3473*11042SErik.Nordmark@Sun.COM 				iptun_drop_pkt(mp, &iptun->iptun_oerrors);
3474*11042SErik.Nordmark@Sun.COM 				return;
3475*11042SErik.Nordmark@Sun.COM 			}
3476*11042SErik.Nordmark@Sun.COM 		}
3477*11042SErik.Nordmark@Sun.COM 
347810616SSebastien.Roy@Sun.COM 		atomic_inc_64(&iptun->iptun_opackets);
3479*11042SErik.Nordmark@Sun.COM 		atomic_add_64(&iptun->iptun_obytes, ixa->ixa_pktlen);
3480*11042SErik.Nordmark@Sun.COM 
3481*11042SErik.Nordmark@Sun.COM 		error = conn_ip_output(mp, ixa);
3482*11042SErik.Nordmark@Sun.COM 		if (error == EMSGSIZE) {
3483*11042SErik.Nordmark@Sun.COM 			/* IPsec policy might have changed */
3484*11042SErik.Nordmark@Sun.COM 			(void) iptun_update_mtu(iptun, ixa, 0);
3485*11042SErik.Nordmark@Sun.COM 		}
348610616SSebastien.Roy@Sun.COM 	}
3487*11042SErik.Nordmark@Sun.COM 	if (ixa->ixa_flags & IXAF_IPSEC_SECURE)
3488*11042SErik.Nordmark@Sun.COM 		ipsec_out_release_refs(ixa);
348910616SSebastien.Roy@Sun.COM }
349010616SSebastien.Roy@Sun.COM 
349110616SSebastien.Roy@Sun.COM static mac_callbacks_t iptun_m_callbacks = {
349210616SSebastien.Roy@Sun.COM 	.mc_callbacks	= (MC_SETPROP | MC_GETPROP),
349310616SSebastien.Roy@Sun.COM 	.mc_getstat	= iptun_m_getstat,
349410616SSebastien.Roy@Sun.COM 	.mc_start	= iptun_m_start,
349510616SSebastien.Roy@Sun.COM 	.mc_stop	= iptun_m_stop,
349610616SSebastien.Roy@Sun.COM 	.mc_setpromisc	= iptun_m_setpromisc,
349710616SSebastien.Roy@Sun.COM 	.mc_multicst	= iptun_m_multicst,
349810616SSebastien.Roy@Sun.COM 	.mc_unicst	= iptun_m_unicst,
349910616SSebastien.Roy@Sun.COM 	.mc_tx		= iptun_m_tx,
350010616SSebastien.Roy@Sun.COM 	.mc_setprop	= iptun_m_setprop,
350110616SSebastien.Roy@Sun.COM 	.mc_getprop	= iptun_m_getprop
350210616SSebastien.Roy@Sun.COM };
3503