changeset 2733:fcaf8e865e03

4963362 routing socket is not zone-aware 6391685 Source interface selection is flawed when sending via default route within a zone. 6423486 Solaris should include an option to disable loopback traffic routing between non-global zones. 6426172 ICMP Destination unreachable from global zone instead non-global 6453678 TCP RST are routed as if they were sent by the global zone 6453732 zones routing check for "next hop reachable from zone" for default routes is flawed
author nordmark
date Thu, 14 Sep 2006 18:05:27 -0700
parents beab32ba5863
children 1a2f23099c2a
files usr/src/uts/common/inet/ip.h usr/src/uts/common/inet/ip/igmp.c usr/src/uts/common/inet/ip/ip.c usr/src/uts/common/inet/ip/ip6.c usr/src/uts/common/inet/ip/ip6_if.c usr/src/uts/common/inet/ip/ip6_ire.c usr/src/uts/common/inet/ip/ip_if.c usr/src/uts/common/inet/ip/ip_ire.c usr/src/uts/common/inet/ip/ip_ndp.c usr/src/uts/common/inet/ip/ip_rts.c usr/src/uts/common/inet/ip/spd.c usr/src/uts/common/inet/ip6.h usr/src/uts/common/inet/ip_if.h usr/src/uts/common/inet/ip_impl.h usr/src/uts/common/inet/ip_ire.h usr/src/uts/common/inet/ipsec_impl.h usr/src/uts/common/inet/tcp.h usr/src/uts/common/inet/tcp/tcp.c usr/src/uts/common/inet/udp/udp.c
diffstat 19 files changed, 806 insertions(+), 272 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/inet/ip.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip.h	Thu Sep 14 18:05:27 2006 -0700
@@ -2885,8 +2885,9 @@
 #define	ip_max_defend			ip_param_arr[52].ip_param_value
 #define	ip_defend_interval		ip_param_arr[53].ip_param_value
 #define	ip_dup_recovery			ip_param_arr[54].ip_param_value
+#define	ip_restrict_interzone_loopback	ip_param_arr[55].ip_param_value
 #ifdef DEBUG
-#define	ipv6_drop_inbound_icmpv6	ip_param_arr[55].ip_param_value
+#define	ipv6_drop_inbound_icmpv6	ip_param_arr[56].ip_param_value
 #else
 #define	ipv6_drop_inbound_icmpv6	0
 #endif
@@ -2979,8 +2980,8 @@
 extern const char *mac_colon_addr(const uint8_t *, size_t, char *, size_t);
 extern void	ip_lwput(queue_t *, mblk_t *);
 extern boolean_t icmp_err_rate_limit(void);
-extern void	icmp_time_exceeded(queue_t *, mblk_t *, uint8_t);
-extern void	icmp_unreachable(queue_t *, mblk_t *, uint8_t);
+extern void	icmp_time_exceeded(queue_t *, mblk_t *, uint8_t, zoneid_t);
+extern void	icmp_unreachable(queue_t *, mblk_t *, uint8_t, zoneid_t);
 extern mblk_t	*ip_add_info(mblk_t *, ill_t *, uint_t);
 extern mblk_t	*ip_bind_v4(queue_t *, mblk_t *, conn_t *);
 extern int	ip_bind_connected(conn_t *, mblk_t *, ipaddr_t *, uint16_t,
@@ -3018,10 +3019,11 @@
 extern void	ip_output(void *, mblk_t *, void *, int);
 extern void	ip_wput_md(queue_t *, mblk_t *, conn_t *);
 
-extern void	ip_wput_ire(queue_t *, mblk_t *, ire_t *, conn_t *, int);
+extern void	ip_wput_ire(queue_t *, mblk_t *, ire_t *, conn_t *, int,
+		    zoneid_t);
 extern void	ip_wput_local(queue_t *, ill_t *, ipha_t *, mblk_t *, ire_t *,
-    int, zoneid_t);
-extern void	ip_wput_multicast(queue_t *, mblk_t *, ipif_t *);
+		    int, zoneid_t);
+extern void	ip_wput_multicast(queue_t *, mblk_t *, ipif_t *, zoneid_t);
 extern void	ip_wput_nondata(ipsq_t *, queue_t *, mblk_t *, void *);
 extern void	ip_wsrv(queue_t *);
 extern char	*ip_nv_lookup(nv_t *, int);
@@ -3029,7 +3031,8 @@
 extern boolean_t ip_remote_addr_ok_v6(const in6_addr_t *, const in6_addr_t *);
 extern ipaddr_t ip_massage_options(ipha_t *);
 extern ipaddr_t ip_net_mask(ipaddr_t);
-extern void	ip_newroute(queue_t *, mblk_t *, ipaddr_t, ill_t *, conn_t *);
+extern void	ip_newroute(queue_t *, mblk_t *, ipaddr_t, ill_t *, conn_t *,
+		    zoneid_t);
 extern ipxmit_state_t	ip_xmit_v4(mblk_t *, ire_t *, struct ipsec_out_s *,
     boolean_t);
 extern int	ip_hdr_complete(ipha_t *, zoneid_t);
--- a/usr/src/uts/common/inet/ip/igmp.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/igmp.c	Thu Sep 14 18:05:27 2006 -0700
@@ -1952,7 +1952,7 @@
 	ASSERT(ill->ill_rq != NULL);
 	ip_multicast_loopback(ill->ill_rq, ill, first_mp, 0, ilm->ilm_zoneid);
 
-	ip_wput_multicast(ill->ill_wq, first_mp, ipif);
+	ip_wput_multicast(ill->ill_wq, first_mp, ipif, zoneid);
 
 	++igmpstat.igps_snd_reports;
 }
@@ -2135,7 +2135,7 @@
 	ASSERT(ill->ill_rq != NULL);
 	ip_multicast_loopback(ill->ill_rq, ill, mp, 0, ipif->ipif_zoneid);
 
-	ip_wput_multicast(ill->ill_wq, first_mp, ipif);
+	ip_wput_multicast(ill->ill_wq, first_mp, ipif, zoneid);
 
 	++igmpstat.igps_snd_reports;
 
--- a/usr/src/uts/common/inet/ip/ip.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip.c	Thu Sep 14 18:05:27 2006 -0700
@@ -632,7 +632,7 @@
  * --------				----------------
  * IRE_BROADCAST			Exclusive
  * IRE_DEFAULT (default routes)		Shared (*)
- * IRE_LOCAL				Exclusive
+ * IRE_LOCAL				Exclusive (x)
  * IRE_LOOPBACK				Exclusive
  * IRE_PREFIX (net routes)		Shared (*)
  * IRE_CACHE				Exclusive
@@ -644,6 +644,22 @@
  * directly reachable from the zone, that is, if the gateway's address matches
  * one of the zone's logical interfaces.
  *
+ * (x) IRE_LOCAL are handled a bit differently, since for all other entries
+ * in ire_ctable and IRE_INTERFACE, ire_src_addr is what can be used as source
+ * when sending packets using the IRE. For IRE_LOCAL ire_src_addr is the IP
+ * address of the zone itself (the destination). Since IRE_LOCAL is used
+ * for communication between zones, ip_wput_ire has special logic to set
+ * the right source address when sending using an IRE_LOCAL.
+ *
+ * Furthermore, when ip_restrict_interzone_loopback is set (the default),
+ * ire_cache_lookup restricts loopback using an IRE_LOCAL
+ * between zone to the case when L2 would have conceptually looped the packet
+ * back, i.e. the loopback which is required since neither Ethernet drivers
+ * nor Ethernet hardware loops them back. This is the case when the normal
+ * routes (ignoring IREs with different zoneids) would send out the packet on
+ * the same ill (or ill group) as the ill with which is IRE_LOCAL is
+ * associated.
+ *
  * Multiple zones can share a common broadcast address; typically all zones
  * share the 255.255.255.255 address. Incoming as well as locally originated
  * broadcast packets must be dispatched to all the zones on the broadcast
@@ -689,7 +705,7 @@
 static mblk_t	*ip_wput_attach_llhdr(mblk_t *, ire_t *, ip_proc_t, uint32_t);
 static void	ip_ipsec_out_prepend(mblk_t *, mblk_t *, ill_t *);
 
-static void	icmp_frag_needed(queue_t *, mblk_t *, int);
+static void	icmp_frag_needed(queue_t *, mblk_t *, int, zoneid_t);
 static void	icmp_inbound(queue_t *, mblk_t *, boolean_t, ill_t *, int,
     uint32_t, boolean_t, boolean_t, ill_t *, zoneid_t);
 static ipaddr_t	icmp_get_nexthop_addr(ipha_t *, ill_t *, zoneid_t, mblk_t *mp);
@@ -699,8 +715,9 @@
 		    icmph_t *, ipha_t *, int, int, boolean_t, boolean_t,
 		    ill_t *, zoneid_t);
 static void	icmp_options_update(ipha_t *);
-static void	icmp_param_problem(queue_t *, mblk_t *, uint8_t);
-static void	icmp_pkt(queue_t *, mblk_t *, void *, size_t, boolean_t);
+static void	icmp_param_problem(queue_t *, mblk_t *, uint8_t, zoneid_t);
+static void	icmp_pkt(queue_t *, mblk_t *, void *, size_t, boolean_t,
+		    zoneid_t zoneid);
 static mblk_t	*icmp_pkt_err_ok(mblk_t *);
 static void	icmp_redirect(mblk_t *);
 static void	icmp_send_redirect(queue_t *, mblk_t *, ipaddr_t);
@@ -722,9 +739,10 @@
 ipaddr_t	ip_massage_options(ipha_t *);
 static void	ip_mrtun_forward(ire_t *, ill_t *, mblk_t *);
 ipaddr_t	ip_net_mask(ipaddr_t);
-void		ip_newroute(queue_t *, mblk_t *, ipaddr_t, ill_t *, conn_t *);
+void		ip_newroute(queue_t *, mblk_t *, ipaddr_t, ill_t *, conn_t *,
+		    zoneid_t);
 static void	ip_newroute_ipif(queue_t *, mblk_t *, ipif_t *, ipaddr_t,
-		    conn_t *, uint32_t);
+		    conn_t *, uint32_t, zoneid_t);
 char		*ip_nv_lookup(nv_t *, int);
 static boolean_t	ip_check_for_ipsec_opt(queue_t *, mblk_t *);
 static int	ip_param_get(queue_t *, mblk_t *, caddr_t, cred_t *);
@@ -766,11 +784,12 @@
 static boolean_t	ip_source_routed(ipha_t *);
 static boolean_t	ip_source_route_included(ipha_t *);
 
-static void	ip_wput_frag(ire_t *, mblk_t *, ip_pkt_t, uint32_t, uint32_t);
+static void	ip_wput_frag(ire_t *, mblk_t *, ip_pkt_t, uint32_t, uint32_t,
+		    zoneid_t);
 static mblk_t	*ip_wput_frag_copyhdr(uchar_t *, int, int);
 static void	ip_wput_local_options(ipha_t *);
 static int	ip_wput_options(queue_t *, mblk_t *, ipha_t *, boolean_t,
-    zoneid_t);
+		    zoneid_t);
 
 static void	conn_drain_init(void);
 static void	conn_drain_fini(void);
@@ -962,6 +981,7 @@
 	{  0,	1000,	3,	"ip_max_defend" },
 	{  0,	999999,	30,	"ip_defend_interval" },
 	{  0,	3600000, 300000, "ip_dup_recovery" },
+	{  0,	1,	1,	"ip_restrict_interzone_loopback" },
 #ifdef DEBUG
 	{  0,	1,	0,	"ip6_drop_inbound_icmpv6" },
 #endif
@@ -1495,6 +1515,38 @@
 #ifdef	DEBUG
 static boolean_t skip_sctp_cksum = B_FALSE;
 #endif
+
+/*
+ * Prepend the zoneid using an ipsec_out_t for later use by functions like
+ * ip_rput_v6(), ip_output(), etc.  If the message
+ * block already has a M_CTL at the front of it, then simply set the zoneid
+ * appropriately.
+ */
+mblk_t *
+ip_prepend_zoneid(mblk_t *mp, zoneid_t zoneid)
+{
+	mblk_t		*first_mp;
+	ipsec_out_t	*io;
+
+	ASSERT(zoneid != ALL_ZONES);
+	if (mp->b_datap->db_type == M_CTL) {
+		io = (ipsec_out_t *)mp->b_rptr;
+		ASSERT(io->ipsec_out_type == IPSEC_OUT);
+		io->ipsec_out_zoneid = zoneid;
+		return (mp);
+	}
+
+	first_mp = ipsec_alloc_ipsec_out();
+	if (first_mp == NULL)
+		return (NULL);
+	io = (ipsec_out_t *)first_mp->b_rptr;
+	/* This is not a secure packet */
+	io->ipsec_out_secure = B_FALSE;
+	io->ipsec_out_zoneid = zoneid;
+	first_mp->b_cont = mp;
+	return (first_mp);
+}
+
 /*
  * Copy an M_CTL-tagged message, preserving reference counts appropriately.
  */
@@ -1527,7 +1579,7 @@
 
 /* Generate an ICMP fragmentation needed message. */
 static void
-icmp_frag_needed(queue_t *q, mblk_t *mp, int mtu)
+icmp_frag_needed(queue_t *q, mblk_t *mp, int mtu, zoneid_t zoneid)
 {
 	icmph_t	icmph;
 	mblk_t *first_mp;
@@ -1547,7 +1599,7 @@
 	icmph.icmph_du_mtu = htons((uint16_t)mtu);
 	BUMP_MIB(&icmp_mib, icmpOutFragNeeded);
 	BUMP_MIB(&icmp_mib, icmpOutDestUnreachs);
-	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present);
+	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present, zoneid);
 }
 
 /*
@@ -3261,7 +3313,7 @@
  * Generate an ICMP parameter problem message.
  */
 static void
-icmp_param_problem(queue_t *q, mblk_t *mp, uint8_t ptr)
+icmp_param_problem(queue_t *q, mblk_t *mp, uint8_t ptr, zoneid_t zoneid)
 {
 	icmph_t	icmph;
 	boolean_t mctl_present;
@@ -3279,7 +3331,7 @@
 	icmph.icmph_type = ICMP_PARAM_PROBLEM;
 	icmph.icmph_pp_ptr = ptr;
 	BUMP_MIB(&icmp_mib, icmpOutParmProbs);
-	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present);
+	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present, zoneid);
 }
 
 /*
@@ -3296,7 +3348,7 @@
  */
 static void
 icmp_pkt(queue_t *q, mblk_t *mp, void *stuff, size_t len,
-    boolean_t mctl_present)
+    boolean_t mctl_present, zoneid_t zoneid)
 {
 	ipaddr_t dst;
 	icmph_t	*icmph;
@@ -3309,7 +3361,6 @@
 	mblk_t *ipsec_mp;
 	ipsec_out_t	*io = NULL;
 	boolean_t xmit_if_on = B_FALSE;
-	zoneid_t	zoneid;
 
 	if (mctl_present) {
 		/*
@@ -3354,7 +3405,7 @@
 			 */
 			io->ipsec_out_proc_begin = B_FALSE;
 		}
-		zoneid = io->ipsec_out_zoneid;
+		ASSERT(zoneid == io->ipsec_out_zoneid);
 		ASSERT(zoneid != ALL_ZONES);
 	} else {
 		/*
@@ -3376,13 +3427,14 @@
 
 		/* This is not a secure packet */
 		ii->ipsec_in_secure = B_FALSE;
-		if (CONN_Q(q)) {
-			zoneid = Q_TO_CONN(q)->conn_zoneid;
-		} else {
-			zoneid = GLOBAL_ZONEID;
-		}
-		ii->ipsec_in_zoneid = zoneid;
-		ASSERT(zoneid != ALL_ZONES);
+		/*
+		 * For trusted extensions using a shared IP address we can
+		 * send using any zoneid.
+		 */
+		if (zoneid == ALL_ZONES)
+			ii->ipsec_in_zoneid = GLOBAL_ZONEID;
+		else
+			ii->ipsec_in_zoneid = zoneid;
 		ipsec_mp->b_cont = mp;
 		ipha = (ipha_t *)mp->b_rptr;
 		/*
@@ -3679,14 +3731,15 @@
 	icmph.icmph_code = 1;
 	icmph.icmph_rd_gateway = gateway;
 	BUMP_MIB(&icmp_mib, icmpOutRedirects);
-	icmp_pkt(q, mp, &icmph, sizeof (icmph_t), B_FALSE);
+	/* Redirects sent by router, and router is global zone */
+	icmp_pkt(q, mp, &icmph, sizeof (icmph_t), B_FALSE, GLOBAL_ZONEID);
 }
 
 /*
  * Generate an ICMP time exceeded message.
  */
 void
-icmp_time_exceeded(queue_t *q, mblk_t *mp, uint8_t code)
+icmp_time_exceeded(queue_t *q, mblk_t *mp, uint8_t code, zoneid_t zoneid)
 {
 	icmph_t	icmph;
 	boolean_t mctl_present;
@@ -3704,14 +3757,14 @@
 	icmph.icmph_type = ICMP_TIME_EXCEEDED;
 	icmph.icmph_code = code;
 	BUMP_MIB(&icmp_mib, icmpOutTimeExcds);
-	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present);
+	icmp_pkt(q, first_mp, &icmph, sizeof (icmph_t), mctl_present, zoneid);
 }
 
 /*
  * Generate an ICMP unreachable message.
  */
 void
-icmp_unreachable(queue_t *q, mblk_t *mp, uint8_t code)
+icmp_unreachable(queue_t *q, mblk_t *mp, uint8_t code, zoneid_t zoneid)
 {
 	icmph_t	icmph;
 	mblk_t *first_mp;
@@ -3730,7 +3783,8 @@
 	icmph.icmph_code = code;
 	BUMP_MIB(&icmp_mib, icmpOutDestUnreachs);
 	ip2dbg(("send icmp destination unreachable code %d\n", code));
-	icmp_pkt(q, first_mp, (char *)&icmph, sizeof (icmph_t), mctl_present);
+	icmp_pkt(q, first_mp, (char *)&icmph, sizeof (icmph_t), mctl_present,
+	    zoneid);
 }
 
 /*
@@ -5968,7 +6022,7 @@
 		}
 		switch (icmp_type) {
 		case ICMP_DEST_UNREACHABLE:
-			icmp_unreachable(WR(q), first_mp, icmp_code);
+			icmp_unreachable(WR(q), first_mp, icmp_code, zoneid);
 			break;
 		default:
 			freemsg(first_mp);
@@ -6382,7 +6436,7 @@
 		BUMP_MIB(&ip_mib, ipInDelivers);
 		ip2dbg(("ip_fanout_tcp: no listener; send reset to zone %d\n",
 		    zoneid));
-		tcp_xmit_listeners_reset(first_mp, ip_hdr_len);
+		tcp_xmit_listeners_reset(first_mp, ip_hdr_len, zoneid);
 		return;
 	}
 
@@ -6426,7 +6480,7 @@
 			return;
 		}
 		if (flags & TH_ACK) {
-			tcp_xmit_listeners_reset(first_mp, ip_hdr_len);
+			tcp_xmit_listeners_reset(first_mp, ip_hdr_len, zoneid);
 			CONN_DEC_REF(connp);
 			return;
 		}
@@ -7197,8 +7251,9 @@
 			goto drop_pkt;
 		}
 		ip_ipsec_out_prepend(first_mp, mp, in_ill);
-		icmp_time_exceeded(q, first_mp, ICMP_TTL_EXCEEDED);
-
+		/* Sent by forwarding path, and router is global zone */
+		icmp_time_exceeded(q, first_mp, ICMP_TTL_EXCEEDED,
+		    GLOBAL_ZONEID);
 		return;
 	}
 
@@ -7241,7 +7296,7 @@
 		ip_ipsec_out_prepend(first_mp, mp, in_ill);
 		mp = first_mp;
 
-		ip_wput_frag(ire, mp, IB_PKT, max_frag, 0);
+		ip_wput_frag(ire, mp, IB_PKT, max_frag, 0, GLOBAL_ZONEID);
 		return;
 	}
 
@@ -7424,7 +7479,8 @@
  *	are not NULL.
  */
 void
-ip_newroute(queue_t *q, mblk_t *mp, ipaddr_t dst, ill_t *in_ill, conn_t *connp)
+ip_newroute(queue_t *q, mblk_t *mp, ipaddr_t dst, ill_t *in_ill, conn_t *connp,
+    zoneid_t zoneid)
 {
 	areq_t	*areq;
 	ipaddr_t gw = 0;
@@ -7453,7 +7509,6 @@
 	boolean_t multirt_resolve_next;
 	boolean_t do_attach_ill = B_FALSE;
 	boolean_t ip_nexthop = B_FALSE;
-	zoneid_t zoneid;
 	tsol_ire_gw_secattr_t *attrp = NULL;
 	tsol_gcgrp_t *gcgrp = NULL;
 	tsol_gcgrp_addr_t ga;
@@ -7466,12 +7521,9 @@
 	EXTRACT_PKT_MP(mp, first_mp, mctl_present);
 	if (mctl_present) {
 		io = (ipsec_out_t *)first_mp->b_rptr;
-		zoneid = io->ipsec_out_zoneid;
+		ASSERT(io->ipsec_out_type == IPSEC_OUT);
+		ASSERT(zoneid == io->ipsec_out_zoneid);
 		ASSERT(zoneid != ALL_ZONES);
-	} else if (connp != NULL) {
-		zoneid = connp->conn_zoneid;
-	} else {
-		zoneid = GLOBAL_ZONEID;
 	}
 
 	ipha = (ipha_t *)mp->b_rptr;
@@ -8654,10 +8706,11 @@
 		ire_refrele(ire);
 	}
 	if (ip_source_routed(ipha)) {
-		icmp_unreachable(q, first_mp, ICMP_SOURCE_ROUTE_FAILED);
-		return;
-	}
-	icmp_unreachable(q, first_mp, ICMP_HOST_UNREACHABLE);
+		icmp_unreachable(q, first_mp, ICMP_SOURCE_ROUTE_FAILED,
+		    zoneid);
+		return;
+	}
+	icmp_unreachable(q, first_mp, ICMP_HOST_UNREACHABLE, zoneid);
 }
 
 /*
@@ -8688,7 +8741,7 @@
  */
 static void
 ip_newroute_ipif(queue_t *q, mblk_t *mp, ipif_t *ipif, ipaddr_t dst,
-    conn_t *connp, uint32_t flags)
+    conn_t *connp, uint32_t flags, zoneid_t zoneid)
 {
 	areq_t	*areq;
 	ire_t	*ire = NULL;
@@ -8709,7 +8762,6 @@
 	mblk_t  *copy_mp = NULL;
 	boolean_t multirt_resolve_next;
 	ipaddr_t ipha_dst;
-	zoneid_t zoneid = (connp != NULL ? connp->conn_zoneid : ALL_ZONES);
 
 	/*
 	 * CGTP goes in a loop which looks up a new ipif, do an ipif_refhold
@@ -9352,7 +9404,7 @@
 		}
 		ire_refrele(ire);
 	}
-	icmp_unreachable(q, first_mp, ICMP_HOST_UNREACHABLE);
+	icmp_unreachable(q, first_mp, ICMP_HOST_UNREACHABLE, zoneid);
 }
 
 /* Name/Value Table Lookup Routine */
@@ -12787,7 +12839,7 @@
 			return (NULL);
 		}
 		if (flags & TH_ACK) {
-			tcp_xmit_listeners_reset(first_mp, ip_hdr_len);
+			tcp_xmit_listeners_reset(first_mp, ip_hdr_len, zoneid);
 			CONN_DEC_REF(connp);
 			return (NULL);
 		}
@@ -12892,7 +12944,7 @@
 		}
 	}
 	BUMP_MIB(&ip_mib, ipInDelivers);
-	tcp_xmit_listeners_reset(first_mp, IPH_HDR_LENGTH(mp->b_rptr));
+	tcp_xmit_listeners_reset(first_mp, IPH_HDR_LENGTH(mp->b_rptr), zoneid);
 	return (NULL);
 ipoptions:
 	if (!ip_options_cksum(q, first_mp, ipha, ire)) {
@@ -13302,7 +13354,7 @@
 		/*
 		 * Now hand the packet to ip_newroute.
 		 */
-		ip_newroute(q, mp, dst, in_ill, NULL);
+		ip_newroute(q, mp, dst, in_ill, NULL, GLOBAL_ZONEID);
 		return (NULL);
 	}
 	ire = ire_forward(dst, &check_multirt, NULL, NULL,
@@ -13310,7 +13362,7 @@
 
 	if (ire == NULL && check_multirt) {
 		/* Let ip_newroute handle CGTP  */
-		ip_newroute(q, mp, dst, in_ill, NULL);
+		ip_newroute(q, mp, dst, in_ill, NULL, GLOBAL_ZONEID);
 		return (NULL);
 	}
 
@@ -13320,10 +13372,13 @@
 	mp->b_prev = mp->b_next = 0;
 	/* send icmp unreachable */
 	q = WR(q);
-	if (ip_source_routed(ipha))
-		icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED);
-	else
-		icmp_unreachable(q, mp, ICMP_HOST_UNREACHABLE);
+	/* Sent by forwarding path, and router is global zone */
+	if (ip_source_routed(ipha)) {
+		icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED,
+		    GLOBAL_ZONEID);
+	} else {
+		icmp_unreachable(q, mp, ICMP_HOST_UNREACHABLE, GLOBAL_ZONEID);
+	}
 
 	return (NULL);
 
@@ -13504,12 +13559,13 @@
 			BUMP_MIB(&ip_mib, ipInDiscards);
 			mp->b_prev = mp->b_next = 0;
 			/* send icmp unreachable */
+			/* Sent by forwarding path, and router is global zone */
 			if (ip_source_routed(ipha)) {
 				icmp_unreachable(ill->ill_wq, mp,
-				    ICMP_SOURCE_ROUTE_FAILED);
+				    ICMP_SOURCE_ROUTE_FAILED, GLOBAL_ZONEID);
 			} else {
 				icmp_unreachable(ill->ill_wq, mp,
-				    ICMP_HOST_UNREACHABLE);
+				    ICMP_HOST_UNREACHABLE, GLOBAL_ZONEID);
 			}
 			return (ire);
 		}
@@ -13633,8 +13689,9 @@
 			 * hardware checksum as we are not using it.
 			 */
 			DB_CKSUMFLAGS(mp) = 0;
+			/* Sent by forwarding path, and router is global zone */
 			icmp_unreachable(q, mp,
-			    ICMP_SOURCE_ROUTE_FAILED);
+			    ICMP_SOURCE_ROUTE_FAILED, GLOBAL_ZONEID);
 			return;
 		}
 		goto drop_pkt;
@@ -16014,9 +16071,11 @@
 		 * multicast packets.
 		 */
 		q = ire->ire_stq;
-		if (q)
-			icmp_time_exceeded(q, mp, ICMP_TTL_EXCEEDED);
-		else
+		if (q != NULL) {
+			/* Sent by forwarding path, and router is global zone */
+			icmp_time_exceeded(q, mp, ICMP_TTL_EXCEEDED,
+			    GLOBAL_ZONEID);
+		} else
 			freemsg(mp);
 		return;
 	}
@@ -16081,7 +16140,7 @@
 				return;
 			}
 		}
-		ip_wput_frag(ire, mp, IB_PKT, max_frag, 0);
+		ip_wput_frag(ire, mp, IB_PKT, max_frag, 0, GLOBAL_ZONEID);
 		ip2dbg(("ip_rput_forward:sent to ip_wput_frag\n"));
 		return;
 	}
@@ -16122,7 +16181,7 @@
 		mp->b_prev = NULL;
 		mp->b_next = mp;
 		ip_newroute_ipif(ipif->ipif_ill->ill_wq, mp, ipif, dst,
-		    NULL, 0);
+		    NULL, 0, GLOBAL_ZONEID);
 	} else {
 		ip_rput_forward(ire, (ipha_t *)mp->b_rptr, mp, NULL);
 		IRE_REFRELE(ire);
@@ -16160,10 +16219,15 @@
 			/* Check if adminstratively disabled */
 			if (!ip_forward_src_routed) {
 				BUMP_MIB(&ip_mib, ipForwProhibits);
-				if (ire->ire_stq)
+				if (ire->ire_stq != NULL) {
+					/*
+					 * Sent by forwarding path, and router
+					 * is global zone
+					 */
 					icmp_unreachable(ire->ire_stq, mp,
-					    ICMP_SOURCE_ROUTE_FAILED);
-				else {
+					    ICMP_SOURCE_ROUTE_FAILED,
+					    GLOBAL_ZONEID);
+				} else {
 					ip0dbg(("ip_rput_forward_options: "
 					    "unable to send unreach\n"));
 					freemsg(mp);
@@ -17089,6 +17153,8 @@
 	uint32_t	ts;
 	ire_t		*dst_ire;
 	timestruc_t	now;
+	zoneid_t	zoneid;
+	ill_t		*ill;
 
 	ASSERT(ire->ire_ipversion == IPV4_VERSION);
 
@@ -17213,9 +17279,18 @@
 
 bad_src_route:
 	q = WR(q);
+	if (q->q_next != NULL)
+		ill = q->q_ptr;
+	else
+		ill = NULL;
+
 	/* make sure we clear any indication of a hardware checksum */
 	DB_CKSUMFLAGS(mp) = 0;
-	icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED);
+	zoneid = ipif_lookup_addr_zoneid(ipha->ipha_dst, ill);
+	if (zoneid == ALL_ZONES)
+		freemsg(mp);
+	else
+		icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED, zoneid);
 	return (B_FALSE);
 
 }
@@ -17236,6 +17311,8 @@
 	ipaddr_t	dst;
 	intptr_t	code = 0;
 	ire_t		*ire = NULL;
+	zoneid_t	zoneid;
+	ill_t		*ill;
 
 	ip2dbg(("ip_rput_options\n"));
 	dst = ipha->ipha_dst;
@@ -17397,16 +17474,35 @@
 
 param_prob:
 	q = WR(q);
+	if (q->q_next != NULL)
+		ill = q->q_ptr;
+	else
+		ill = NULL;
+
 	/* make sure we clear any indication of a hardware checksum */
 	DB_CKSUMFLAGS(mp) = 0;
-	icmp_param_problem(q, mp, (uint8_t)code);
+	/* Don't know whether this is for non-global or global/forwarding */
+	zoneid = ipif_lookup_addr_zoneid(dst, ill);
+	if (zoneid == ALL_ZONES)
+		freemsg(mp);
+	else
+		icmp_param_problem(q, mp, (uint8_t)code, zoneid);
 	return (-1);
 
 bad_src_route:
 	q = WR(q);
+	if (q->q_next != NULL)
+		ill = q->q_ptr;
+	else
+		ill = NULL;
+
 	/* make sure we clear any indication of a hardware checksum */
 	DB_CKSUMFLAGS(mp) = 0;
-	icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED);
+	zoneid = ipif_lookup_addr_zoneid(dst, ill);
+	if (zoneid == ALL_ZONES)
+		freemsg(mp);
+	else
+		icmp_unreachable(q, mp, ICMP_SOURCE_ROUTE_FAILED, zoneid);
 	return (-1);
 }
 
@@ -17756,7 +17852,8 @@
 	rw_enter(&ill_g_lock, RW_READER);
 	ill = ILL_START_WALK_V6(&ctx);
 	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
-		for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
 			if (ipif->ipif_zoneid != zoneid &&
 			    ipif->ipif_zoneid != ALL_ZONES)
 				continue;
@@ -19181,6 +19278,11 @@
 /*
  * Write side put procedure.  Outbound data, IOCTLs, responses from
  * resolvers, etc, come down through here.
+ *
+ * arg2 is always a queue_t *.
+ * When that queue is an ill_t (i.e. q_next != NULL), then arg must be
+ * the zoneid.
+ * When that queue is not an ill_t, then arg must be a conn_t pointer.
  */
 void
 ip_output(void *arg, mblk_t *mp, void *arg2, int caller)
@@ -19227,11 +19329,14 @@
 	 */
 
 	/* is packet from ARP ? */
-	if (q->q_next != NULL)
+	if (q->q_next != NULL) {
+		zoneid = (zoneid_t)(uintptr_t)arg;
 		goto qnext;
+	}
 
 	connp = (conn_t *)arg;
-	zoneid = (connp != NULL ? connp->conn_zoneid : ALL_ZONES);
+	ASSERT(connp != NULL);
+	zoneid = connp->conn_zoneid;
 
 	/* is queue flow controlled? */
 	if ((q->q_first != NULL || connp->conn_draining) &&
@@ -19468,14 +19573,14 @@
 			}
 		}
 
-		ip_wput_ire(q, first_mp, ire, connp, caller);
+		ip_wput_ire(q, first_mp, ire, connp, caller, zoneid);
 
 		/*
 		 * Try to resolve another multiroute if
 		 * ire_multirt_need_resolve() deemed it necessary.
 		 */
 		if (copy_mp != NULL) {
-			ip_newroute(q, copy_mp, dst, NULL, connp);
+			ip_newroute(q, copy_mp, dst, NULL, connp, zoneid);
 		}
 		if (need_decref)
 			CONN_DEC_REF(connp);
@@ -19597,14 +19702,14 @@
 		}
 	}
 
-	ip_wput_ire(q, first_mp, ire, connp, caller);
+	ip_wput_ire(q, first_mp, ire, connp, caller, zoneid);
 
 	/*
 	 * Try to resolve another multiroute if
 	 * ire_multirt_resolvable() deemed it necessary
 	 */
 	if (copy_mp != NULL) {
-		ip_newroute(q, copy_mp, dst, NULL, connp);
+		ip_newroute(q, copy_mp, dst, NULL, connp, zoneid);
 	}
 	if (need_decref)
 		CONN_DEC_REF(connp);
@@ -19899,7 +20004,7 @@
 				ill_refrele(attach_ill);
 			if (need_decref)
 				mp->b_flag |= MSGHASREF;
-			(void) ip_output_v6(connp, first_mp, q, caller);
+			(void) ip_output_v6(arg, first_mp, arg2, caller);
 			return;
 		}
 
@@ -20207,7 +20312,7 @@
 			if (xmit_ill == NULL ||
 			    xmit_ill->ill_ipif_up_count > 0) {
 				ip_newroute_ipif(q, first_mp, ipif, dst, connp,
-				    setsrc | RTF_MULTIRT);
+				    setsrc | RTF_MULTIRT, zoneid);
 				TRACE_2(TR_FAC_IP, TR_IP_WPUT_END,
 				    "ip_wput_end: q %p (%S)", q, "noire");
 			} else {
@@ -20382,7 +20487,7 @@
 				ipif = ipif_get_next_ipif(NULL, xmit_ill);
 				if (ipif != NULL) {
 					ip_newroute_ipif(q, first_mp, ipif,
-					    dst, connp, 0);
+					    dst, connp, 0, zoneid);
 					ipif_refrele(ipif);
 					ip1dbg(("ip_wput: ip_unicast_if\n"));
 				}
@@ -20463,7 +20568,7 @@
 			 */
 			mp->b_prev = NULL;
 			mp->b_next = NULL;
-			ip_newroute(q, first_mp, dst, NULL, connp);
+			ip_newroute(q, first_mp, dst, NULL, connp, zoneid);
 			TRACE_2(TR_FAC_IP, TR_IP_WPUT_END,
 			    "ip_wput_end: q %p (%S)", q, "newroute");
 			if (attach_ill != NULL)
@@ -20522,7 +20627,7 @@
 		}
 	}
 
-	ip_wput_ire(q, first_mp, ire, connp, caller);
+	ip_wput_ire(q, first_mp, ire, connp, caller, zoneid);
 	/*
 	 * Try to resolve another multiroute if
 	 * ire_multirt_resolvable() deemed it necessary.
@@ -20536,7 +20641,7 @@
 			ipif_t *ipif = ipif_lookup_group(dst, zoneid);
 			if (ipif) {
 				ip_newroute_ipif(q, copy_mp, ipif, dst, connp,
-				    RTF_SETSRC | RTF_MULTIRT);
+				    RTF_SETSRC | RTF_MULTIRT, zoneid);
 				ipif_refrele(ipif);
 			} else {
 				MULTIRT_DEBUG_UNTAG(copy_mp);
@@ -20544,7 +20649,7 @@
 				copy_mp = NULL;
 			}
 		} else {
-			ip_newroute(q, copy_mp, dst, NULL, connp);
+			ip_newroute(q, copy_mp, dst, NULL, connp, zoneid);
 		}
 	}
 	if (attach_ill != NULL)
@@ -20561,7 +20666,7 @@
 	if (ip_hdr_complete(ipha, zoneid) == 0) {
 		BUMP_MIB(&ip_mib, ipOutNoRoutes);
 		/* it's the IP header length that's in trouble */
-		icmp_param_problem(q, first_mp, 0);
+		icmp_param_problem(q, first_mp, 0, zoneid);
 		first_mp = NULL;
 	}
 
@@ -20580,10 +20685,20 @@
 	    "ip_wput_end: q %p (%S)", q, "droppkt");
 }
 
+/*
+ * If this is a conn_t queue, then we pass in the conn. This includes the
+ * zoneid.
+ * Otherwise, this is a message coming back from ARP or for an ill_t queue,
+ * in which case we use the global zoneid since those are all part of
+ * the global zone.
+ */
 void
 ip_wput(queue_t *q, mblk_t *mp)
 {
-	ip_output(Q_TO_CONN(q), mp, q, IP_WPUT);
+	if (CONN_Q(q))
+		ip_output(Q_TO_CONN(q), mp, q, IP_WPUT);
+	else
+		ip_output(GLOBAL_ZONEID, mp, q, IP_WPUT);
 }
 
 /*
@@ -20693,7 +20808,7 @@
  * NOTE : This function does not ire_refrele the ire argument passed in.
  */
 static void
-ip_wput_ire_fragmentit(mblk_t *ipsec_mp, ire_t *ire)
+ip_wput_ire_fragmentit(mblk_t *ipsec_mp, ire_t *ire, zoneid_t zoneid)
 {
 	ipha_t		*ipha;
 	mblk_t		*mp;
@@ -20751,7 +20866,7 @@
 	    ip_source_route_included(ipha)) || CLASSD(ipha->ipha_dst));
 
 	ip_wput_frag(ire, ipsec_mp, OB_PKT, max_frag,
-	    (dont_use ? 0 : frag_flag));
+	    (dont_use ? 0 : frag_flag), zoneid);
 }
 
 /*
@@ -20863,7 +20978,7 @@
 
 mblk_t *
 ip_wput_ire_parse_ipsec_out(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h, ire_t *ire,
-    conn_t *connp, boolean_t unspec_src)
+    conn_t *connp, boolean_t unspec_src, zoneid_t zoneid)
 {
 	ipsec_out_t	*io;
 	mblk_t		*first_mp;
@@ -20897,7 +21012,7 @@
 		if ((io->ipsec_out_latch == NULL) &&
 		    (io->ipsec_out_use_global_policy)) {
 			return (ip_wput_attach_policy(first_mp, ipha, ip6h,
-			    ire, connp, unspec_src));
+				    ire, connp, unspec_src, zoneid));
 		}
 		if (!io->ipsec_out_secure) {
 			/*
@@ -20956,7 +21071,8 @@
 	if (!policy_present)
 		return (mp);
 
-	return (ip_wput_attach_policy(mp, ipha, ip6h, ire, connp, unspec_src));
+	return (ip_wput_attach_policy(mp, ipha, ip6h, ire, connp, unspec_src,
+		    zoneid));
 }
 
 ire_t *
@@ -21061,7 +21177,8 @@
  *
  */
 void
-ip_wput_ire(queue_t *q, mblk_t *mp, ire_t *ire, conn_t *connp, int caller)
+ip_wput_ire(queue_t *q, mblk_t *mp, ire_t *ire, conn_t *connp, int caller,
+    zoneid_t zoneid)
 {
 	ipha_t		*ipha;
 #define	rptr	((uchar_t *)ipha)
@@ -21094,7 +21211,6 @@
 	uint32_t 	ill_index = 0;
 	boolean_t	multirt_send = B_FALSE;
 	int		err;
-	zoneid_t	zoneid;
 	ipxmit_state_t	pktxmit_state;
 
 	TRACE_1(TR_FAC_IP, TR_IP_WPUT_IRE_START,
@@ -21177,11 +21293,10 @@
 
 	if (mp->b_datap->db_type != M_CTL) {
 		ipha = (ipha_t *)mp->b_rptr;
-		zoneid = (connp != NULL ? connp->conn_zoneid : ALL_ZONES);
 	} else {
 		io = (ipsec_out_t *)mp->b_rptr;
 		ASSERT(io->ipsec_out_type == IPSEC_OUT);
-		zoneid = io->ipsec_out_zoneid;
+		ASSERT(zoneid == io->ipsec_out_zoneid);
 		ASSERT(zoneid != ALL_ZONES);
 		ipha = (ipha_t *)mp->b_cont->b_rptr;
 		dst = ipha->ipha_dst;
@@ -21216,12 +21331,24 @@
 		 * matching route in the forwarding table.
 		 * RTF_REJECT and RTF_BLACKHOLE are handled just like
 		 * ip_newroute() does.
-		 */
-		ire_t *src_ire = ire_ftable_lookup(ipha->ipha_dst, 0, 0, 0,
+		 * Note that IRE_LOCAL are special, since they are used
+		 * when the zoneid doesn't match in some cases. This means that
+		 * we need to handle ipha_src differently since ire_src_addr
+		 * belongs to the receiving zone instead of the sending zone.
+		 * When ip_restrict_interzone_loopback is set, then
+		 * ire_cache_lookup() ensures that IRE_LOCAL are only used
+		 * for loopback between zones when the logical "Ethernet" would
+		 * have looped them back.
+		 */
+		ire_t *src_ire;
+
+		src_ire = ire_ftable_lookup(ipha->ipha_dst, 0, 0, 0,
 		    NULL, NULL, zoneid, 0, NULL, (MATCH_IRE_RECURSIVE |
 		    MATCH_IRE_DEFAULT | MATCH_IRE_RJ_BHOLE));
 		if (src_ire != NULL &&
-		    !(src_ire->ire_flags & (RTF_REJECT | RTF_BLACKHOLE))) {
+		    !(src_ire->ire_flags & (RTF_REJECT | RTF_BLACKHOLE)) &&
+		    (!ip_restrict_interzone_loopback ||
+		    ire_local_same_ill_group(ire, src_ire))) {
 			if (ipha->ipha_src == INADDR_ANY && !unspec_src)
 				ipha->ipha_src = src_ire->ire_src_addr;
 			ire_refrele(src_ire);
@@ -21243,7 +21370,7 @@
 				freemsg(mp);
 				return;
 			}
-			icmp_unreachable(q, mp, ICMP_HOST_UNREACHABLE);
+			icmp_unreachable(q, mp, ICMP_HOST_UNREACHABLE, zoneid);
 			return;
 		}
 	}
@@ -21251,7 +21378,7 @@
 	if (mp->b_datap->db_type == M_CTL ||
 	    ipsec_outbound_v4_policy_present) {
 		mp = ip_wput_ire_parse_ipsec_out(mp, ipha, NULL, ire, connp,
-		    unspec_src);
+		    unspec_src, zoneid);
 		if (mp == NULL) {
 			ire_refrele(ire);
 			if (conn_outgoing_ill != NULL)
@@ -21269,7 +21396,8 @@
 		mp = first_mp->b_cont;
 		ipsec_len = ipsec_out_extra_length(first_mp);
 		ASSERT(ipsec_len >= 0);
-		zoneid = io->ipsec_out_zoneid;
+		/* We already picked up the zoneid from the M_CTL above */
+		ASSERT(zoneid == io->ipsec_out_zoneid);
 		ASSERT(zoneid != ALL_ZONES);
 
 		/*
@@ -22260,7 +22388,7 @@
 					ipha->ipha_hdr_checksum =
 					    (uint16_t)ip_csum_hdr(ipha);
 					icmp_frag_needed(ire->ire_stq, first_mp,
-					    max_frag);
+					    max_frag, zoneid);
 					if (!next_mp) {
 						ire_refrele(ire);
 						if (conn_outgoing_ill != NULL) {
@@ -22331,13 +22459,14 @@
 					TRACE_2(TR_FAC_IP, TR_IP_WPUT_IRE_END,
 					    "ip_wput_ire_end: q %p (%S)",
 					    q, "last fragmentation");
-					ip_wput_ire_fragmentit(mp, ire);
+					ip_wput_ire_fragmentit(mp, ire,
+					    zoneid);
 					ire_refrele(ire);
 					if (conn_outgoing_ill != NULL)
 						ill_refrele(conn_outgoing_ill);
 					return;
 				}
-				ip_wput_ire_fragmentit(mp, ire);
+				ip_wput_ire_fragmentit(mp, ire, zoneid);
 			}
 		}
 	} else {
@@ -22899,7 +23028,7 @@
  */
 static void
 ip_wput_frag(ire_t *ire, mblk_t *mp_orig, ip_pkt_t pkt_type, uint32_t max_frag,
-    uint32_t frag_flag)
+    uint32_t frag_flag, zoneid_t zoneid)
 {
 	int		i1;
 	mblk_t		*ll_hdr_mp;
@@ -22979,7 +23108,7 @@
 		 */
 		ipha->ipha_hdr_checksum = 0;
 		ipha->ipha_hdr_checksum = ip_csum_hdr(ipha);
-		icmp_frag_needed(ire->ire_stq, first_mp, max_frag);
+		icmp_frag_needed(ire->ire_stq, first_mp, max_frag, zoneid);
 		TRACE_1(TR_FAC_IP, TR_IP_WPUT_FRAG_END,
 		    "ip_wput_frag_end:(%S)",
 		    "don't fragment");
@@ -24078,7 +24207,7 @@
  * Caller verifies that this isn't a PHYI_LOOPBACK.
  */
 void
-ip_wput_multicast(queue_t *q, mblk_t *mp, ipif_t *ipif)
+ip_wput_multicast(queue_t *q, mblk_t *mp, ipif_t *ipif, zoneid_t zoneid)
 {
 	ipha_t	*ipha;
 	ire_t	*ire;
@@ -24117,7 +24246,7 @@
 	 * ipsec_out_attach_if so that this will not be load spread in
 	 * ip_newroute_ipif.
 	 */
-	ire = ire_ctable_lookup(dst, 0, 0, ipif, ALL_ZONES, NULL,
+	ire = ire_ctable_lookup(dst, 0, 0, ipif, zoneid, NULL,
 	    MATCH_IRE_ILL);
 	if (!ire) {
 		/*
@@ -24127,7 +24256,8 @@
 		 */
 		mp->b_prev = NULL;
 		mp->b_next = NULL;
-		ip_newroute_ipif(q, first_mp, ipif, dst, NULL, RTF_SETSRC);
+		ip_newroute_ipif(q, first_mp, ipif, dst, NULL, RTF_SETSRC,
+		    zoneid);
 		return;
 	}
 
@@ -24141,7 +24271,7 @@
 		ipha->ipha_src = ire->ire_src_addr;
 	}
 
-	ip_wput_ire(q, first_mp, ire, NULL, B_FALSE);
+	ip_wput_ire(q, first_mp, ire, NULL, B_FALSE, zoneid);
 }
 
 /*
@@ -24675,7 +24805,8 @@
 		 *
 		 * Also handle RTF_MULTIRT routes.
 		 */
-		ip_newroute_ipif(q, ipsec_mp, ipif, dst, NULL, RTF_MULTIRT);
+		ip_newroute_ipif(q, ipsec_mp, ipif, dst, NULL, RTF_MULTIRT,
+		    zoneid);
 	} else {
 		if (attach_if) {
 			ire = ire_ctable_lookup(dst, 0, 0, ill->ill_ipif,
@@ -24729,7 +24860,7 @@
 		 */
 		ipha->ipha_ident = IP_HDR_INCLUDED;
 		ip_newroute(q, ipsec_mp, dst, NULL,
-		    (CONN_Q(q) ? Q_TO_CONN(q) : NULL));
+		    (CONN_Q(q) ? Q_TO_CONN(q) : NULL), zoneid);
 	}
 	goto done;
 send:
@@ -24808,7 +24939,7 @@
 			    "fragmented accelerated packet!\n"));
 			freemsg(ipsec_mp);
 		} else {
-			ip_wput_ire_fragmentit(ipsec_mp, ire);
+			ip_wput_ire_fragmentit(ipsec_mp, ire, zoneid);
 		}
 		if (ire_need_rele)
 			ire_refrele(ire);
@@ -26729,7 +26860,7 @@
 		freemsg(ipsec_mp);
 		return (-1);
 	}
-	icmp_param_problem(q, ipsec_mp, (uint8_t)code);
+	icmp_param_problem(q, ipsec_mp, (uint8_t)code, zoneid);
 	return (-1);
 
 bad_src_route:
@@ -26742,7 +26873,7 @@
 		freemsg(ipsec_mp);
 		return (-1);
 	}
-	icmp_unreachable(q, ipsec_mp, ICMP_SOURCE_ROUTE_FAILED);
+	icmp_unreachable(q, ipsec_mp, ICMP_SOURCE_ROUTE_FAILED, zoneid);
 	return (-1);
 }
 
@@ -27050,6 +27181,8 @@
 		connp->conn_draining = 1;
 		noenable(q);
 		while ((mp = getq(q)) != NULL) {
+			ASSERT(CONN_Q(q));
+
 			ip_output(Q_TO_CONN(q), mp, q, IP_WSRV);
 			if (connp->conn_did_putbq) {
 				/* ip_wput did a putbq */
--- a/usr/src/uts/common/inet/ip/ip6.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip6.c	Thu Sep 14 18:05:27 2006 -0700
@@ -248,7 +248,7 @@
 static void	icmp_inbound_too_big_v6(queue_t *, mblk_t *, ill_t *ill,
     boolean_t, zoneid_t);
 static void	icmp_pkt_v6(queue_t *, mblk_t *, void *, size_t,
-    const in6_addr_t *, boolean_t);
+    const in6_addr_t *, boolean_t, zoneid_t);
 static void	icmp_redirect_v6(queue_t *, mblk_t *, ill_t *ill);
 static boolean_t	icmp_redirect_ok_v6(ill_t *ill, mblk_t *mp);
 static int	ip_bind_connected_v6(conn_t *, mblk_t *, in6_addr_t *,
@@ -270,7 +270,7 @@
     ip6_frag_t *, uint_t, uint_t *, uint32_t *, uint16_t *);
 static boolean_t	ip_source_routed_v6(ip6_t *, mblk_t *);
 static void	ip_wput_ire_v6(queue_t *, mblk_t *, ire_t *, int, int,
-    conn_t *, int, int, int);
+    conn_t *, int, int, int, zoneid_t);
 static boolean_t ip_ulp_cando_pkt2big(int);
 
 static void ip_rput_v6(queue_t *, mblk_t *);
@@ -1540,20 +1540,17 @@
  */
 static in6_addr_t *
 icmp_pick_source_v6(queue_t *wq, in6_addr_t *origsrc, in6_addr_t *origdst,
-    in6_addr_t *src)
+    in6_addr_t *src, zoneid_t zoneid)
 {
 	ill_t	*ill;
 	ire_t	*ire;
 	ipif_t	*ipif;
-	zoneid_t	zoneid;
 
 	ASSERT(!(wq->q_flag & QREADR));
 	if (wq->q_next != NULL) {
 		ill = (ill_t *)wq->q_ptr;
-		zoneid = GLOBAL_ZONEID;
 	} else {
 		ill = NULL;
-		zoneid = Q_TO_CONN(wq)->conn_zoneid;
 	}
 
 	ire = ire_route_lookup_v6(origdst, 0, 0, (IRE_LOCAL|IRE_LOOPBACK),
@@ -1625,7 +1622,7 @@
  */
 static void
 icmp_pkt_v6(queue_t *q, mblk_t *mp, void *stuff, size_t len,
-    const in6_addr_t *v6src_ptr, boolean_t mctl_present)
+    const in6_addr_t *v6src_ptr, boolean_t mctl_present, zoneid_t zoneid)
 {
 	ip6_t		*ip6h;
 	in6_addr_t	v6dst;
@@ -1703,6 +1700,14 @@
 
 		/* This is not a secure packet */
 		ii->ipsec_in_secure = B_FALSE;
+		/*
+		 * For trusted extensions using a shared IP address we can
+		 * send using any zoneid.
+		 */
+		if (zoneid == ALL_ZONES)
+			ii->ipsec_in_zoneid = GLOBAL_ZONEID;
+		else
+			ii->ipsec_in_zoneid = zoneid;
 		ipsec_mp->b_cont = mp;
 		ip6h = (ip6_t *)mp->b_rptr;
 		/*
@@ -1720,7 +1725,7 @@
 		v6src = *v6src_ptr;
 	} else {
 		if (icmp_pick_source_v6(q, &ip6h->ip6_src, &ip6h->ip6_dst,
-		    &v6src) == NULL) {
+		    &v6src, zoneid) == NULL) {
 			freemsg(ipsec_mp);
 			ill_refrele(ill);
 			return;
@@ -2041,7 +2046,8 @@
 		srcp = &ill->ill_ipif->ipif_v6src_addr;
 	}
 	rw_exit(&ill_g_lock);
-	icmp_pkt_v6(q, mp, buf, len, srcp, B_FALSE);
+	/* Redirects sent by router, and router is global zone */
+	icmp_pkt_v6(q, mp, buf, len, srcp, B_FALSE, GLOBAL_ZONEID);
 	kmem_free(buf, len);
 }
 
@@ -2049,7 +2055,7 @@
 /* Generate an ICMP time exceeded message.  (May be called as writer.) */
 void
 icmp_time_exceeded_v6(queue_t *q, mblk_t *mp, uint8_t code,
-    boolean_t llbcast, boolean_t mcast_ok)
+    boolean_t llbcast, boolean_t mcast_ok, zoneid_t zoneid)
 {
 	icmp6_t	icmp6;
 	boolean_t mctl_present;
@@ -2066,7 +2072,8 @@
 	bzero(&icmp6, sizeof (icmp6_t));
 	icmp6.icmp6_type = ICMP6_TIME_EXCEEDED;
 	icmp6.icmp6_code = code;
-	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present);
+	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present,
+	    zoneid);
 }
 
 /*
@@ -2074,7 +2081,7 @@
  */
 void
 icmp_unreachable_v6(queue_t *q, mblk_t *mp, uint8_t code,
-    boolean_t llbcast, boolean_t mcast_ok)
+    boolean_t llbcast, boolean_t mcast_ok, zoneid_t zoneid)
 {
 	icmp6_t	icmp6;
 	boolean_t mctl_present;
@@ -2091,7 +2098,8 @@
 	bzero(&icmp6, sizeof (icmp6_t));
 	icmp6.icmp6_type = ICMP6_DST_UNREACH;
 	icmp6.icmp6_code = code;
-	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present);
+	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present,
+	    zoneid);
 }
 
 /*
@@ -2099,7 +2107,7 @@
  */
 static void
 icmp_pkt2big_v6(queue_t *q, mblk_t *mp, uint32_t mtu,
-    boolean_t llbcast, boolean_t mcast_ok)
+    boolean_t llbcast, boolean_t mcast_ok, zoneid_t zoneid)
 {
 	icmp6_t	icmp6;
 	mblk_t *first_mp;
@@ -2118,7 +2126,8 @@
 	icmp6.icmp6_code = 0;
 	icmp6.icmp6_mtu = htonl(mtu);
 
-	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present);
+	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present,
+	    zoneid);
 }
 
 /*
@@ -2127,7 +2136,7 @@
  */
 static void
 icmp_param_problem_v6(queue_t *q, mblk_t *mp, uint8_t code,
-    uint32_t offset, boolean_t llbcast, boolean_t mcast_ok)
+    uint32_t offset, boolean_t llbcast, boolean_t mcast_ok, zoneid_t zoneid)
 {
 	icmp6_t	icmp6;
 	boolean_t mctl_present;
@@ -2145,7 +2154,8 @@
 	icmp6.icmp6_type = ICMP6_PARAM_PROB;
 	icmp6.icmp6_code = code;
 	icmp6.icmp6_pptr = htonl(offset);
-	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present);
+	icmp_pkt_v6(q, first_mp, &icmp6, sizeof (icmp6_t), NULL, mctl_present,
+	    zoneid);
 }
 
 /*
@@ -3487,11 +3497,11 @@
 		switch (icmp_type) {
 		case ICMP6_DST_UNREACH:
 			icmp_unreachable_v6(WR(q), first_mp, icmp_code,
-			    B_FALSE, B_FALSE);
+			    B_FALSE, B_FALSE, zoneid);
 			break;
 		case ICMP6_PARAM_PROB:
 			icmp_param_problem_v6(WR(q), first_mp, icmp_code,
-			    nexthdr_offset, B_FALSE, B_FALSE);
+			    nexthdr_offset, B_FALSE, B_FALSE, zoneid);
 			break;
 		default:
 #ifdef DEBUG
@@ -3557,7 +3567,7 @@
 			}
 		}
 		BUMP_MIB(ill->ill_ip6_mib, ipv6InDelivers);
-		tcp_xmit_listeners_reset(first_mp, hdr_len);
+		tcp_xmit_listeners_reset(first_mp, hdr_len, zoneid);
 		if (connp != NULL)
 			CONN_DEC_REF(connp);
 		return;
@@ -3603,7 +3613,7 @@
 			return;
 		}
 		if (flags & TH_ACK) {
-			tcp_xmit_listeners_reset(first_mp, hdr_len);
+			tcp_xmit_listeners_reset(first_mp, hdr_len, zoneid);
 			CONN_DEC_REF(connp);
 			return;
 		}
@@ -5710,7 +5720,7 @@
 		    AF_INET6, v6dstp);
 	}
 	icmp_unreachable_v6(WR(q), first_mp, ICMP6_DST_UNREACH_NOROUTE,
-	    B_FALSE, B_FALSE);
+	    B_FALSE, B_FALSE, zoneid);
 }
 
 /*
@@ -6394,6 +6404,8 @@
 	int ret = 0;
 	mblk_t *first_mp;
 	const char *errtype;
+	zoneid_t zoneid;
+	ill_t *ill = q->q_ptr;
 
 	first_mp = mp;
 	if (mp->b_datap->db_type == M_CTL) {
@@ -6531,6 +6543,9 @@
 				errtype = "unknown";
 				/* FALLTHROUGH */
 			opt_error:
+				/* Determine which zone should send error */
+				zoneid = ipif_lookup_addr_zoneid_v6(
+				    &ip6h->ip6_dst, ill);
 				switch (IP6OPT_TYPE(opt_type)) {
 				case IP6OPT_TYPE_SKIP:
 					optused = 2 + optptr[1];
@@ -6547,18 +6562,26 @@
 					freemsg(first_mp);
 					return (-1);
 				case IP6OPT_TYPE_ICMP:
+					if (zoneid == ALL_ZONES) {
+						freemsg(first_mp);
+						return (-1);
+					}
 					icmp_param_problem_v6(WR(q), first_mp,
 					    ICMP6_PARAMPROB_OPTION,
 					    (uint32_t)(optptr -
 					    (uint8_t *)ip6h),
-					    B_FALSE, B_FALSE);
+					    B_FALSE, B_FALSE, zoneid);
 					return (-1);
 				case IP6OPT_TYPE_FORCEICMP:
+					if (zoneid == ALL_ZONES) {
+						freemsg(first_mp);
+						return (-1);
+					}
 					icmp_param_problem_v6(WR(q), first_mp,
 					    ICMP6_PARAMPROB_OPTION,
 					    (uint32_t)(optptr -
 					    (uint8_t *)ip6h),
-					    B_FALSE, B_TRUE);
+					    B_FALSE, B_TRUE, zoneid);
 					return (-1);
 				default:
 					ASSERT(0);
@@ -6571,9 +6594,15 @@
 	return (ret);
 
 bad_opt:
-	icmp_param_problem_v6(WR(q), first_mp, ICMP6_PARAMPROB_OPTION,
-	    (uint32_t)(optptr - (uint8_t *)ip6h),
-	    B_FALSE, B_FALSE);
+	/* Determine which zone should send error */
+	zoneid = ipif_lookup_addr_zoneid_v6(&ip6h->ip6_dst, ill);
+	if (zoneid == ALL_ZONES) {
+		freemsg(first_mp);
+	} else {
+		icmp_param_problem_v6(WR(q), first_mp, ICMP6_PARAMPROB_OPTION,
+		    (uint32_t)(optptr - (uint8_t *)ip6h),
+		    B_FALSE, B_FALSE, zoneid);
+	}
 	return (-1);
 }
 
@@ -6605,10 +6634,11 @@
 	if (rth->ip6r_type != 0) {
 		if (hada_mp != NULL)
 			goto hada_drop;
+		/* Sent by forwarding path, and router is global zone */
 		icmp_param_problem_v6(WR(q), mp,
 		    ICMP6_PARAMPROB_HEADER,
 		    (uint32_t)((uchar_t *)&rth->ip6r_type - (uchar_t *)ip6h),
-		    B_FALSE, B_FALSE);
+		    B_FALSE, B_FALSE, GLOBAL_ZONEID);
 		return;
 	}
 	rthdr = (ip6_rthdr0_t *)rth;
@@ -6620,10 +6650,11 @@
 		/* An odd length is impossible */
 		if (hada_mp != NULL)
 			goto hada_drop;
+		/* Sent by forwarding path, and router is global zone */
 		icmp_param_problem_v6(WR(q), mp,
 		    ICMP6_PARAMPROB_HEADER,
 		    (uint32_t)((uchar_t *)&rthdr->ip6r0_len - (uchar_t *)ip6h),
-		    B_FALSE, B_FALSE);
+		    B_FALSE, B_FALSE, GLOBAL_ZONEID);
 		return;
 	}
 	numaddr = rthdr->ip6r0_len / 2;
@@ -6631,11 +6662,12 @@
 		/* segleft exceeds number of addresses in routing header */
 		if (hada_mp != NULL)
 			goto hada_drop;
+		/* Sent by forwarding path, and router is global zone */
 		icmp_param_problem_v6(WR(q), mp,
 		    ICMP6_PARAMPROB_HEADER,
 		    (uint32_t)((uchar_t *)&rthdr->ip6r0_segleft -
 			(uchar_t *)ip6h),
-		    B_FALSE, B_FALSE);
+		    B_FALSE, B_FALSE, GLOBAL_ZONEID);
 		return;
 	}
 	addrptr += (numaddr - rthdr->ip6r0_segleft);
@@ -6655,8 +6687,9 @@
 	if (IN6_IS_ADDR_V4MAPPED(&ip6h->ip6_dst)) {
 		if (hada_mp != NULL)
 			goto hada_drop;
+		/* Sent by forwarding path, and router is global zone */
 		icmp_unreachable_v6(WR(q), mp, ICMP6_DST_UNREACH_NOROUTE,
-		    B_FALSE, B_FALSE);
+		    B_FALSE, B_FALSE, GLOBAL_ZONEID);
 		return;
 	}
 	ip_rput_data_v6(q, ill, mp, ip6h, flags, hada_mp, dl_mp);
@@ -7385,8 +7418,10 @@
 		if (ip6h->ip6_hops <= 1) {
 			if (hada_mp != NULL)
 				goto hada_drop;
+			/* Sent by forwarding path, and router is global zone */
 			icmp_time_exceeded_v6(WR(q), first_mp,
-			    ICMP6_TIME_EXCEED_TRANSIT, ll_multicast, B_FALSE);
+			    ICMP6_TIME_EXCEED_TRANSIT, ll_multicast, B_FALSE,
+			    GLOBAL_ZONEID);
 			return;
 		}
 		/*
@@ -7442,8 +7477,10 @@
 		}
 		if (ip6h->ip6_hops <= 1) {
 			ip1dbg(("ip_rput_data_v6: hop limit expired.\n"));
+			/* Sent by forwarding path, and router is global zone */
 			icmp_time_exceeded_v6(WR(q), mp,
-			    ICMP6_TIME_EXCEED_TRANSIT, ll_multicast, B_FALSE);
+			    ICMP6_TIME_EXCEED_TRANSIT, ll_multicast, B_FALSE,
+			    GLOBAL_ZONEID);
 			ire_refrele(ire);
 			return;
 		}
@@ -7475,8 +7512,9 @@
 
 		if (pkt_len > ire->ire_max_frag) {
 			BUMP_MIB(ill->ill_ip6_mib, ipv6InTooBigErrors);
+			/* Sent by forwarding path, and router is global zone */
 			icmp_pkt2big_v6(WR(q), mp, ire->ire_max_frag,
-			    ll_multicast, B_TRUE);
+			    ll_multicast, B_TRUE, GLOBAL_ZONEID);
 			ire_refrele(ire);
 			return;
 		}
@@ -8176,7 +8214,7 @@
 			icmp_param_problem_v6(WR(q), first_mp,
 			    ICMP6_PARAMPROB_NEXTHEADER,
 			    prev_nexthdr_offset,
-			    B_FALSE, B_FALSE);
+			    B_FALSE, B_FALSE, zoneid);
 			return;
 
 		case IPPROTO_ROUTING: {
@@ -8503,10 +8541,17 @@
 	 * of eight?
 	 */
 	if (more_frags && (ntohs(ip6h->ip6_plen) & 7)) {
+		zoneid_t zoneid;
+
 		BUMP_MIB(ill->ill_ip6_mib, ipv6InHdrErrors);
+		zoneid = ipif_lookup_addr_zoneid_v6(&ip6h->ip6_dst, ill);
+		if (zoneid == ALL_ZONES) {
+			freemsg(mp);
+			return (NULL);
+		}
 		icmp_param_problem_v6(WR(q), mp, ICMP6_PARAMPROB_HEADER,
 		    (uint32_t)((char *)&ip6h->ip6_plen -
-		    (char *)ip6h), B_FALSE, B_FALSE);
+		    (char *)ip6h), B_FALSE, B_FALSE, zoneid);
 		return (NULL);
 	}
 
@@ -8522,10 +8567,17 @@
 	 * greater than IP_MAXPACKET - the max payload size?
 	 */
 	if (end > IP_MAXPACKET) {
+		zoneid_t	zoneid;
+
 		BUMP_MIB(ill->ill_ip6_mib, ipv6InHdrErrors);
+		zoneid = ipif_lookup_addr_zoneid_v6(&ip6h->ip6_dst, ill);
+		if (zoneid == ALL_ZONES) {
+			freemsg(mp);
+			return (NULL);
+		}
 		icmp_param_problem_v6(WR(q), mp, ICMP6_PARAMPROB_HEADER,
 		    (uint32_t)((char *)&fraghdr->ip6f_offlg -
-		    (char *)ip6h), B_FALSE, B_FALSE);
+		    (char *)ip6h), B_FALSE, B_FALSE, zoneid);
 		return (NULL);
 	}
 
@@ -9131,6 +9183,11 @@
  *    look for the best IRE match for the unspecified group to determine
  *    the ill.
  * 7. For unicast: Just do an IRE lookup for the best match.
+ *
+ * arg2 is always a queue_t *.
+ * When that queue is an ill_t (i.e. q_next != NULL), then arg must be
+ * the zoneid.
+ * When that queue is not an ill_t, then arg must be a conn_t pointer.
  */
 void
 ip_output_v6(void *arg, mblk_t *mp, void *arg2, int caller)
@@ -9239,7 +9296,7 @@
 
 		if ((mlen == sizeof (ipsec_ctl_t)) &&
 		    (mctltype == IPSEC_CTL)) {
-			ip_output(Q_TO_CONN(q), first_mp, q, caller);
+			ip_output(arg, first_mp, arg2, caller);
 			return;
 		}
 
@@ -9313,9 +9370,11 @@
 		unspec_src = 0;
 		BUMP_MIB(mibptr, ipv6OutRequests);
 		do_outrequests = B_FALSE;
+		zoneid = (zoneid_t)(uintptr_t)arg;
 	} else {
 		connp = (conn_t *)arg;
 		ASSERT(connp != NULL);
+		zoneid = connp->conn_zoneid;
 
 		/* is queue flow controlled? */
 		if ((q->q_first || connp->conn_draining) &&
@@ -9394,10 +9453,13 @@
 	 * prepended ipsec_out_t.
 	 */
 	if (io != NULL) {
+		/*
+		 * When coming from icmp_input_v6, the zoneid might not match
+		 * for the loopback case, because inside icmp_input_v6 the
+		 * queue_t is a conn queue from the sending side.
+		 */
 		zoneid = io->ipsec_out_zoneid;
 		ASSERT(zoneid != ALL_ZONES);
-	} else {
-		zoneid = (connp != NULL ? connp->conn_zoneid : ALL_ZONES);
 	}
 
 	if (ip6h->ip6_nxt == IPPROTO_RAW) {
@@ -9832,7 +9894,7 @@
 			}
 		}
 		ip_wput_ire_v6(q, first_mp, ire, unspec_src, cksum_request,
-		    connp, caller, 0, ip6i_flags);
+		    connp, caller, 0, ip6i_flags, zoneid);
 		if (need_decref) {
 			CONN_DEC_REF(connp);
 			connp = NULL;
@@ -10253,7 +10315,7 @@
 		ip_wput_ire_v6(q, first_mp, ire, unspec_src, cksum_request,
 		    connp, caller,
 		    (attach_if ? ill->ill_phyint->phyint_ifindex : 0),
-		    ip6i_flags);
+		    ip6i_flags, zoneid);
 		ire_refrele(ire);
 		if (need_decref) {
 			CONN_DEC_REF(connp);
@@ -10390,15 +10452,25 @@
 		}
 	}
 	BUMP_MIB(mibptr, ipv6OutIPv4);
-	(void) ip_output(connp, first_mp, q, caller);
+	(void) ip_output(arg, first_mp, arg2, caller);
 	if (ill != NULL)
 		ill_refrele(ill);
 }
 
+/*
+ * If this is a conn_t queue, then we pass in the conn. This includes the
+ * zoneid.
+ * Otherwise, this is a message for an ill_t queue,
+ * in which case we use the global zoneid since those are all part of
+ * the global zone.
+ */
 static void
 ip_wput_v6(queue_t *q, mblk_t *mp)
 {
-	ip_output_v6(Q_TO_CONN(q), mp, q, IP_WPUT);
+	if (CONN_Q(q))
+		ip_output_v6(Q_TO_CONN(q), mp, q, IP_WPUT);
+	else
+		ip_output_v6(GLOBAL_ZONEID, mp, q, IP_WPUT);
 }
 
 static void
@@ -10647,7 +10719,8 @@
  */
 static void
 ip_wput_ire_v6(queue_t *q, mblk_t *mp, ire_t *ire, int unspec_src,
-    int cksum_request, conn_t *connp, int caller, int attach_index, int flags)
+    int cksum_request, conn_t *connp, int caller, int attach_index, int flags,
+    zoneid_t zoneid)
 {
 	ip6_t		*ip6h;
 	uint8_t		nexthdr;
@@ -10662,9 +10735,7 @@
 	boolean_t	conn_multicast_loop;	/* conn value for multicast */
 	boolean_t 	multicast_forward;	/* Should we forward ? */
 	int		max_frag;
-	zoneid_t	zoneid;
-
-	zoneid = (connp != NULL ? connp->conn_zoneid : ALL_ZONES);
+
 	ill = ire_to_ill(ire);
 	first_mp = mp;
 	multicast_forward = B_FALSE;
@@ -10678,7 +10749,7 @@
 		 * Grab the zone id now because the M_CTL can be discarded by
 		 * ip_wput_ire_parse_ipsec_out() below.
 		 */
-		zoneid = io->ipsec_out_zoneid;
+		ASSERT(zoneid == io->ipsec_out_zoneid);
 		ASSERT(zoneid != ALL_ZONES);
 		ip6h = (ip6_t *)first_mp->b_cont->b_rptr;
 		/*
@@ -10723,12 +10794,24 @@
 		 * matching route in the forwarding table.
 		 * RTF_REJECT and RTF_BLACKHOLE are handled just like
 		 * ip_newroute_v6() does.
-		 */
-		ire_t *src_ire = ire_ftable_lookup_v6(&ip6h->ip6_dst, 0, 0, 0,
+		 * Note that IRE_LOCAL are special, since they are used
+		 * when the zoneid doesn't match in some cases. This means that
+		 * we need to handle ipha_src differently since ire_src_addr
+		 * belongs to the receiving zone instead of the sending zone.
+		 * When ip_restrict_interzone_loopback is set, then
+		 * ire_cache_lookup_v6() ensures that IRE_LOCAL are only used
+		 * for loopback between zones when the logical "Ethernet" would
+		 * have looped them back.
+		 */
+		ire_t *src_ire;
+
+		src_ire = ire_ftable_lookup_v6(&ip6h->ip6_dst, 0, 0, 0,
 		    NULL, NULL, zoneid, 0, NULL, (MATCH_IRE_RECURSIVE |
 		    MATCH_IRE_DEFAULT | MATCH_IRE_RJ_BHOLE));
 		if (src_ire != NULL &&
-		    !(src_ire->ire_flags & (RTF_REJECT | RTF_BLACKHOLE))) {
+		    !(src_ire->ire_flags & (RTF_REJECT | RTF_BLACKHOLE)) &&
+		    (!ip_restrict_interzone_loopback ||
+		    ire_local_same_ill_group(ire, src_ire))) {
 			if (IN6_IS_ADDR_UNSPECIFIED(&ip6h->ip6_src) &&
 			    !unspec_src) {
 				ip6h->ip6_src = src_ire->ire_src_addr_v6;
@@ -10750,14 +10833,15 @@
 				return;
 			}
 			icmp_unreachable_v6(q, first_mp,
-			    ICMP6_DST_UNREACH_NOROUTE, B_FALSE, B_FALSE);
+			    ICMP6_DST_UNREACH_NOROUTE, B_FALSE, B_FALSE,
+			    zoneid);
 			return;
 		}
 	}
 
 	if (mp->b_datap->db_type == M_CTL || ipsec_outbound_v6_policy_present) {
 		mp = ip_wput_ire_parse_ipsec_out(first_mp, NULL, ip6h, ire,
-		    connp, unspec_src);
+		    connp, unspec_src, zoneid);
 		if (mp == NULL) {
 			return;
 		}
@@ -11174,7 +11258,7 @@
 		    (ire->ire_frag_flag & IPH_FRAG_HDR)) {
 			if (connp != NULL && (flags & IP6I_DONTFRAG)) {
 				icmp_pkt2big_v6(ire->ire_stq, first_mp,
-				    max_frag, B_FALSE, B_TRUE);
+				    max_frag, B_FALSE, B_TRUE, zoneid);
 				return;
 			}
 
@@ -11225,7 +11309,7 @@
 				 * generate.
 				 */
 				icmp_pkt2big_v6(ire->ire_stq, first_mp,
-				    max_frag, B_FALSE, B_TRUE);
+				    max_frag, B_FALSE, B_TRUE, zoneid);
 				return;
 			}
 			if (attach_index != 0)
--- a/usr/src/uts/common/inet/ip/ip6_if.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip6_if.c	Thu Sep 14 18:05:27 2006 -0700
@@ -144,7 +144,8 @@
 	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
 		GRAB_CONN_LOCK(q);
 		mutex_enter(&ill->ill_lock);
-		for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
 			/* Allow the ipif to be down */
 			if ((ipif->ipif_flags & IPIF_POINTOPOINT) &&
 			    (IN6_ARE_ADDR_EQUAL(&ipif->ipif_v6lcl_addr,
@@ -216,7 +217,8 @@
 		}
 		GRAB_CONN_LOCK(q);
 		mutex_enter(&ill->ill_lock);
-		for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
 			if (zoneid != ALL_ZONES &&
 			    ipif->ipif_zoneid != zoneid &&
 			    ipif->ipif_zoneid != ALL_ZONES)
@@ -252,7 +254,7 @@
 		RELEASE_CONN_LOCK(q);
 	}
 
-	/* Repeat once more if needed */
+	/* If we already did the ptp case, then we are done */
 	if (ptp) {
 		rw_exit(&ill_g_lock);
 		if (error != NULL)
@@ -264,6 +266,70 @@
 }
 
 /*
+ * Look for an ipif with the specified address. For point-point links
+ * we look for matches on either the destination address and the local
+ * address, but we ignore the check on the local address if IPIF_UNNUMBERED
+ * is set.
+ * Matches on a specific ill if match_ill is set.
+ * Return the zoneid for the ipif. ALL_ZONES if none found.
+ */
+zoneid_t
+ipif_lookup_addr_zoneid_v6(const in6_addr_t *addr, ill_t *match_ill)
+{
+	ipif_t	*ipif;
+	ill_t	*ill;
+	boolean_t  ptp = B_FALSE;
+	ill_walk_context_t ctx;
+	zoneid_t	zoneid;
+
+	rw_enter(&ill_g_lock, RW_READER);
+	/*
+	 * Repeat twice, first based on local addresses and
+	 * next time for pointopoint.
+	 */
+repeat:
+	ill = ILL_START_WALK_V6(&ctx);
+	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
+		if (match_ill != NULL && ill != match_ill) {
+			continue;
+		}
+		mutex_enter(&ill->ill_lock);
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
+			/* Allow the ipif to be down */
+			if ((!ptp && (IN6_ARE_ADDR_EQUAL(
+			    &ipif->ipif_v6lcl_addr, addr) &&
+			    (ipif->ipif_flags & IPIF_UNNUMBERED) == 0)) ||
+			    (ptp && (ipif->ipif_flags & IPIF_POINTOPOINT) &&
+			    IN6_ARE_ADDR_EQUAL(&ipif->ipif_v6pp_dst_addr,
+			    addr)) &&
+			    !(ipif->ipif_state_flags & IPIF_CONDEMNED)) {
+				zoneid = ipif->ipif_zoneid;
+				mutex_exit(&ill->ill_lock);
+				rw_exit(&ill_g_lock);
+				/*
+				 * If ipif_zoneid was ALL_ZONES then we have
+				 * a trusted extensions shared IP address.
+				 * In that case GLOBAL_ZONEID works to send.
+				 */
+				if (zoneid == ALL_ZONES)
+					zoneid = GLOBAL_ZONEID;
+				return (zoneid);
+			}
+		}
+		mutex_exit(&ill->ill_lock);
+	}
+
+	/* If we already did the ptp case, then we are done */
+	if (ptp) {
+		rw_exit(&ill_g_lock);
+		return (ALL_ZONES);
+	}
+	ptp = B_TRUE;
+	goto repeat;
+}
+
+/*
  * Perform various checks to verify that an address would make sense as a local
  * interface address.  This is currently only called when an attempt is made
  * to set a local address.
--- a/usr/src/uts/common/inet/ip/ip6_ire.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip6_ire.c	Thu Sep 14 18:05:27 2006 -0700
@@ -1882,8 +1882,10 @@
 					 * ire_route_lookup_v6() is avoided when
 					 * we have only one default route.
 					 */
+					match_flags |= MATCH_IRE_TYPE;
 					rire = ire_route_lookup_v6(&gw_addr_v6,
-					    NULL, NULL, 0, ire->ire_ipif, NULL,
+					    NULL, NULL, IRE_INTERFACE,
+					    ire->ire_ipif, NULL,
 					    zoneid, tsl, match_flags);
 					if (rire != NULL) {
 						ire_refrele(rire);
@@ -2152,6 +2154,19 @@
  * Lookup cache. Don't return IRE_MARK_HIDDEN entries. Callers
  * should use ire_ctable_lookup with MATCH_IRE_MARK_HIDDEN to get
  * to the hidden ones.
+ *
+ * In general the zoneid has to match (where ALL_ZONES match all of them).
+ * But for IRE_LOCAL we also need to handle the case where L2 should
+ * conceptually loop back the packet. This is necessary since neither
+ * Ethernet drivers nor Ethernet hardware loops back packets sent to their
+ * own MAC address. This loopback is needed when the normal
+ * routes (ignoring IREs with different zoneids) would send out the packet on
+ * the same ill (or ill group) as the ill with which this IRE_LOCAL is
+ * associated.
+ *
+ * Earlier versions of this code always matched an IRE_LOCAL independently of
+ * the zoneid. We preserve that earlier behavior when
+ * ip_restrict_interzone_loopback is turned off.
  */
 ire_t *
 ire_cache_lookup_v6(const in6_addr_t *addr, zoneid_t zoneid,
@@ -2179,8 +2194,18 @@
 			}
 
 			if (zoneid == ALL_ZONES || ire->ire_zoneid == zoneid ||
-			    ire->ire_zoneid == ALL_ZONES ||
-			    ire->ire_type == IRE_LOCAL) {
+			    ire->ire_zoneid == ALL_ZONES) {
+				IRE_REFHOLD(ire);
+				rw_exit(&irb_ptr->irb_lock);
+				return (ire);
+			}
+
+			if (ire->ire_type == IRE_LOCAL) {
+				if (ip_restrict_interzone_loopback &&
+				    !ire_local_ok_across_zones(ire, zoneid,
+				    (void *)addr, tsl))
+					continue;
+
 				IRE_REFHOLD(ire);
 				rw_exit(&irb_ptr->irb_lock);
 				return (ire);
--- a/usr/src/uts/common/inet/ip/ip_if.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip_if.c	Thu Sep 14 18:05:27 2006 -0700
@@ -3514,6 +3514,7 @@
 	uint32_t	hdr_length;
 	mblk_t	*send_icmp_head;
 	mblk_t	*send_icmp_head_v6;
+	zoneid_t zoneid;
 
 	ipfb = ill->ill_frag_hash_tbl;
 	if (ipfb == NULL)
@@ -3594,18 +3595,44 @@
 		 * above.
 		 */
 		while (send_icmp_head_v6 != NULL) {
+			ip6_t *ip6h;
+
 			mp = send_icmp_head_v6;
 			send_icmp_head_v6 = send_icmp_head_v6->b_next;
 			mp->b_next = NULL;
-			icmp_time_exceeded_v6(ill->ill_wq, mp,
-			    ICMP_REASSEMBLY_TIME_EXCEEDED, B_FALSE, B_FALSE);
+			if (mp->b_datap->db_type == M_CTL)
+				ip6h = (ip6_t *)mp->b_cont->b_rptr;
+			else
+				ip6h = (ip6_t *)mp->b_rptr;
+			zoneid = ipif_lookup_addr_zoneid_v6(&ip6h->ip6_dst,
+			    ill);
+			if (zoneid == ALL_ZONES) {
+				freemsg(mp);
+			} else {
+				icmp_time_exceeded_v6(ill->ill_wq, mp,
+				    ICMP_REASSEMBLY_TIME_EXCEEDED, B_FALSE,
+				    B_FALSE, zoneid);
+			}
 		}
 		while (send_icmp_head != NULL) {
+			ipaddr_t dst;
+
 			mp = send_icmp_head;
 			send_icmp_head = send_icmp_head->b_next;
 			mp->b_next = NULL;
-			icmp_time_exceeded(ill->ill_wq, mp,
-			    ICMP_REASSEMBLY_TIME_EXCEEDED);
+
+			if (mp->b_datap->db_type == M_CTL)
+				dst = ((ipha_t *)mp->b_cont->b_rptr)->ipha_dst;
+			else
+				dst = ((ipha_t *)mp->b_rptr)->ipha_dst;
+
+			zoneid = ipif_lookup_addr_zoneid(dst, ill);
+			if (zoneid == ALL_ZONES) {
+				freemsg(mp);
+			} else {
+				icmp_time_exceeded(ill->ill_wq, mp,
+				    ICMP_REASSEMBLY_TIME_EXCEEDED, zoneid);
+			}
 		}
 	}
 	/*
@@ -5136,7 +5163,8 @@
 	rw_enter(&ill_g_lock, RW_READER);
 	ill = ILL_START_WALK_ALL(&ctx);
 	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
-		for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
 			if (zoneid != GLOBAL_ZONEID &&
 			    zoneid != ipif->ipif_zoneid &&
 			    ipif->ipif_zoneid != ALL_ZONES)
@@ -5564,7 +5592,7 @@
 		RELEASE_CONN_LOCK(q);
 	}
 
-	/* Now try the ptp case */
+	/* If we already did the ptp case, then we are done */
 	if (ptp) {
 		rw_exit(&ill_g_lock);
 		if (error != NULL)
@@ -5576,6 +5604,68 @@
 }
 
 /*
+ * Look for an ipif with the specified address. For point-point links
+ * we look for matches on either the destination address and the local
+ * address, but we ignore the check on the local address if IPIF_UNNUMBERED
+ * is set.
+ * Matches on a specific ill if match_ill is set.
+ * Return the zoneid for the ipif which matches. ALL_ZONES if no match.
+ */
+zoneid_t
+ipif_lookup_addr_zoneid(ipaddr_t addr, ill_t *match_ill)
+{
+	zoneid_t zoneid;
+	ipif_t  *ipif;
+	ill_t   *ill;
+	boolean_t ptp = B_FALSE;
+	ill_walk_context_t	ctx;
+
+	rw_enter(&ill_g_lock, RW_READER);
+	/*
+	 * Repeat twice, first based on local addresses and
+	 * next time for pointopoint.
+	 */
+repeat:
+	ill = ILL_START_WALK_V4(&ctx);
+	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
+		if (match_ill != NULL && ill != match_ill) {
+			continue;
+		}
+		mutex_enter(&ill->ill_lock);
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
+			/* Allow the ipif to be down */
+			if ((!ptp && (ipif->ipif_lcl_addr == addr) &&
+			    ((ipif->ipif_flags & IPIF_UNNUMBERED) == 0)) ||
+			    (ptp && (ipif->ipif_flags & IPIF_POINTOPOINT) &&
+			    (ipif->ipif_pp_dst_addr == addr)) &&
+			    !(ipif->ipif_state_flags & IPIF_CONDEMNED)) {
+				zoneid = ipif->ipif_zoneid;
+				mutex_exit(&ill->ill_lock);
+				rw_exit(&ill_g_lock);
+				/*
+				 * If ipif_zoneid was ALL_ZONES then we have
+				 * a trusted extensions shared IP address.
+				 * In that case GLOBAL_ZONEID works to send.
+				 */
+				if (zoneid == ALL_ZONES)
+					zoneid = GLOBAL_ZONEID;
+				return (zoneid);
+			}
+		}
+		mutex_exit(&ill->ill_lock);
+	}
+
+	/* If we already did the ptp case, then we are done */
+	if (ptp) {
+		rw_exit(&ill_g_lock);
+		return (ALL_ZONES);
+	}
+	ptp = B_TRUE;
+	goto repeat;
+}
+
+/*
  * Look for an ipif that matches the specified remote address i.e. the
  * ipif that would receive the specified packet.
  * First look for directly connected interfaces and then do a recursive
@@ -8201,7 +8291,7 @@
 	rw_enter(&ill_g_lock, RW_READER);
 	ill = ILL_START_WALK_V4(&ctx);
 	for (; ill != NULL; ill = ill_next(&ctx, ill)) {
-		for (ipif = ill->ill_ipif; ipif;
+		for (ipif = ill->ill_ipif; ipif != NULL;
 		    ipif = ipif->ipif_next) {
 			if (zoneid != ipif->ipif_zoneid &&
 			    ipif->ipif_zoneid != ALL_ZONES)
@@ -10364,7 +10454,8 @@
 
 	if (found_sep && orig_ifindex == 0) {
 		/* Now see if there is an IPIF with this unit number. */
-		for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+		for (ipif = ill->ill_ipif; ipif != NULL;
+		    ipif = ipif->ipif_next) {
 			if (ipif->ipif_id == id) {
 				err = EEXIST;
 				goto done;
@@ -18560,7 +18651,7 @@
 	GRAB_CONN_LOCK(q);
 	mutex_enter(&ill->ill_lock);
 	/* Now see if there is an IPIF with this unit number. */
-	for (ipif = ill->ill_ipif; ipif; ipif = ipif->ipif_next) {
+	for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next) {
 		if (ipif->ipif_id == id) {
 			if (zoneid != ALL_ZONES &&
 			    zoneid != ipif->ipif_zoneid &&
--- a/usr/src/uts/common/inet/ip/ip_ire.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip_ire.c	Thu Sep 14 18:05:27 2006 -0700
@@ -1055,12 +1055,20 @@
 	boolean_t is_secure;
 	uint_t ifindex;
 	ill_t	*ill;
+	zoneid_t zoneid = ire->ire_zoneid;
 
 	ASSERT(ire->ire_ipversion == IPV4_VERSION);
+	ASSERT(!(ire->ire_type & IRE_LOCAL)); /* Has different ire_zoneid */
 	ipsec_mp = pkt;
 	is_secure = (pkt->b_datap->db_type == M_CTL);
-	if (is_secure)
+	if (is_secure) {
+		ipsec_out_t *io;
+
 		pkt = pkt->b_cont;
+		io = (ipsec_out_t *)ipsec_mp->b_rptr;
+		if (io->ipsec_out_type == IPSEC_OUT)
+			zoneid = io->ipsec_out_zoneid;
+	}
 
 	/* If the packet originated externally then */
 	if (pkt->b_prev) {
@@ -1132,7 +1140,13 @@
 		if (ipha->ipha_dst != ire->ire_addr &&
 		    !(ire->ire_marks & IRE_MARK_NOADD)) {
 			ire_refrele(ire);	/* Held in ire_add */
-			(void) ip_output(Q_TO_CONN(q), ipsec_mp, q, IRE_SEND);
+			if (CONN_Q(q)) {
+				(void) ip_output(Q_TO_CONN(q), ipsec_mp, q,
+				    IRE_SEND);
+			} else {
+				(void) ip_output((void *)(uintptr_t)zoneid,
+				    ipsec_mp, q, IRE_SEND);
+			}
 		} else {
 			if (is_secure) {
 				ipsec_out_t *oi;
@@ -1158,14 +1172,14 @@
 					 * ip_wput_ire.
 					 */
 					ip_wput_ire(q, ipsec_mp, ire, NULL,
-					    IRE_SEND);
+					    IRE_SEND, zoneid);
 				}
 			} else {
 				/*
 				 * IRE_REFRELE will be done in ip_wput_ire.
 				 */
 				ip_wput_ire(q, ipsec_mp, ire, NULL,
-				    IRE_SEND);
+				    IRE_SEND, zoneid);
 			}
 		}
 		/*
@@ -1213,12 +1227,19 @@
 	mblk_t *ipsec_mp;
 	boolean_t secure;
 	uint_t ifindex;
+	zoneid_t zoneid = ire->ire_zoneid;
 
 	ASSERT(ire->ire_ipversion == IPV6_VERSION);
+	ASSERT(!(ire->ire_type & IRE_LOCAL)); /* Has different ire_zoneid */
 	if (pkt->b_datap->db_type == M_CTL) {
+		ipsec_out_t *io;
+
 		ipsec_mp = pkt;
 		pkt = pkt->b_cont;
 		secure = B_TRUE;
+		io = (ipsec_out_t *)ipsec_mp->b_rptr;
+		if (io->ipsec_out_type == IPSEC_OUT)
+			zoneid = io->ipsec_out_zoneid;
 	} else {
 		ipsec_mp = pkt;
 		secure = B_FALSE;
@@ -1284,16 +1305,27 @@
 				ip_wput_ipsec_out_v6(q, ipsec_mp, ip6h,
 				    NULL, NULL);
 			} else {
-				(void) ip_output_v6(Q_TO_CONN(q), ipsec_mp,
-				    q, IRE_SEND);
+				if (CONN_Q(q)) {
+					(void) ip_output_v6(Q_TO_CONN(q),
+					    ipsec_mp, q, IRE_SEND);
+				} else {
+					(void) ip_output_v6(
+					    (void *)(uintptr_t)zoneid,
+					    ipsec_mp, q, IRE_SEND);
+				}
 			}
 		} else {
 			/*
 			 * Send packets through ip_output_v6 so that any
 			 * ip6_info header can be processed again.
 			 */
-			(void) ip_output_v6(Q_TO_CONN(q), ipsec_mp, q,
-			    IRE_SEND);
+			if (CONN_Q(q)) {
+				(void) ip_output_v6(Q_TO_CONN(q), ipsec_mp, q,
+				    IRE_SEND);
+			} else {
+				(void) ip_output_v6((void *)(uintptr_t)zoneid,
+				    ipsec_mp, q, IRE_SEND);
+			}
 		}
 		/*
 		 * Special code to support sending a single packet with
@@ -1566,7 +1598,8 @@
 				 * ire->ire_zoneid.
 				 */
 				ip_newroute(q, mp, ipha->ipha_dst, 0,
-				    (CONN_Q(q) ? Q_TO_CONN(q) : NULL));
+				    (CONN_Q(q) ? Q_TO_CONN(q) : NULL),
+				    ire->ire_zoneid);
 			} else {
 				ASSERT(ire->ire_ipversion == IPV6_VERSION);
 				ip_newroute_v6(q, mp, &ip6h->ip6_dst, NULL,
@@ -2656,28 +2689,30 @@
 
 		/*
 		 * Match all default routes from the global zone, irrespective
-		 * of reachability.
+		 * of reachability. For a non-global zone only match those
+		 * where ire_gateway_addr has a IRE_INTERFACE for the zoneid.
 		 */
 		if (ire->ire_type == IRE_DEFAULT && zoneid != GLOBAL_ZONEID) {
 			int ire_match_flags = 0;
 			in6_addr_t gw_addr_v6;
 			ire_t *rire;
 
+			ire_match_flags |= MATCH_IRE_TYPE;
 			if (ire->ire_ipif != NULL) {
 				ire_match_flags |= MATCH_IRE_ILL_GROUP;
 			}
 			if (ire->ire_ipversion == IPV4_VERSION) {
 				rire = ire_route_lookup(ire->ire_gateway_addr,
-				    0, 0, 0, ire->ire_ipif, NULL, zoneid, NULL,
-				    ire_match_flags);
+				    0, 0, IRE_INTERFACE, ire->ire_ipif, NULL,
+				    zoneid, NULL, ire_match_flags);
 			} else {
 				ASSERT(ire->ire_ipversion == IPV6_VERSION);
 				mutex_enter(&ire->ire_lock);
 				gw_addr_v6 = ire->ire_gateway_addr_v6;
 				mutex_exit(&ire->ire_lock);
 				rire = ire_route_lookup_v6(&gw_addr_v6,
-				    NULL, NULL, 0, ire->ire_ipif, NULL, zoneid,
-				    NULL, ire_match_flags);
+				    NULL, NULL, IRE_INTERFACE, ire->ire_ipif,
+				    NULL, zoneid, NULL, ire_match_flags);
 			}
 			if (rire == NULL) {
 				return (B_FALSE);
@@ -4777,9 +4812,85 @@
 }
 
 /*
+ * Check whether the IRE_LOCAL and the IRE potentially used to transmit
+ * (could be an IRE_CACHE, IRE_BROADCAST, or IRE_INTERFACE) are part of
+ * the same ill group.
+ */
+boolean_t
+ire_local_same_ill_group(ire_t *ire_local, ire_t *xmit_ire)
+{
+	ill_t		*recv_ill, *xmit_ill;
+	ill_group_t	*recv_group, *xmit_group;
+
+	ASSERT(ire_local->ire_type == IRE_LOCAL);
+	ASSERT(ire_local->ire_rfq != NULL);
+	ASSERT(xmit_ire->ire_type & (IRE_CACHE|IRE_BROADCAST|IRE_INTERFACE));
+	ASSERT(xmit_ire->ire_stq != NULL);
+	ASSERT(xmit_ire->ire_ipif != NULL);
+
+	recv_ill = ire_local->ire_rfq->q_ptr;
+	xmit_ill = xmit_ire->ire_stq->q_ptr;
+
+	if (recv_ill == xmit_ill)
+		return (B_TRUE);
+
+	recv_group = recv_ill->ill_group;
+	xmit_group = xmit_ill->ill_group;
+
+	if (recv_group != NULL && recv_group == xmit_group)
+		return (B_TRUE);
+
+	return (B_FALSE);
+}
+
+/*
+ * Check if the IRE_LOCAL uses the same ill (group) as another route would use.
+ */
+boolean_t
+ire_local_ok_across_zones(ire_t *ire_local, zoneid_t zoneid, void *addr,
+    const ts_label_t *tsl)
+{
+	ire_t		*alt_ire;
+	boolean_t	rval;
+
+	if (ire_local->ire_ipversion == IPV4_VERSION) {
+		alt_ire = ire_ftable_lookup(*((ipaddr_t *)addr), 0, 0, 0, NULL,
+		    NULL, zoneid, 0, tsl,
+		    MATCH_IRE_RECURSIVE | MATCH_IRE_DEFAULT |
+		    MATCH_IRE_RJ_BHOLE);
+	} else {
+		alt_ire = ire_ftable_lookup_v6((in6_addr_t *)addr, NULL, NULL,
+		    0, NULL, NULL, zoneid, 0, tsl,
+		    MATCH_IRE_RECURSIVE | MATCH_IRE_DEFAULT |
+		    MATCH_IRE_RJ_BHOLE);
+	}
+
+	if (alt_ire == NULL)
+		return (B_FALSE);
+
+	rval = ire_local_same_ill_group(ire_local, alt_ire);
+
+	ire_refrele(alt_ire);
+	return (rval);
+}
+
+/*
  * Lookup cache. Don't return IRE_MARK_HIDDEN entries. Callers
  * should use ire_ctable_lookup with MATCH_IRE_MARK_HIDDEN to get
  * to the hidden ones.
+ *
+ * In general the zoneid has to match (where ALL_ZONES match all of them).
+ * But for IRE_LOCAL we also need to handle the case where L2 should
+ * conceptually loop back the packet. This is necessary since neither
+ * Ethernet drivers nor Ethernet hardware loops back packets sent to their
+ * own MAC address. This loopback is needed when the normal
+ * routes (ignoring IREs with different zoneids) would send out the packet on
+ * the same ill (or ill group) as the ill with which this IRE_LOCAL is
+ * associated.
+ *
+ * Earlier versions of this code always matched an IRE_LOCAL independently of
+ * the zoneid. We preserve that earlier behavior when
+ * ip_restrict_interzone_loopback is turned off.
  */
 ire_t *
 ire_cache_lookup(ipaddr_t addr, zoneid_t zoneid, const ts_label_t *tsl)
@@ -4807,8 +4918,18 @@
 			}
 
 			if (zoneid == ALL_ZONES || ire->ire_zoneid == zoneid ||
-			    ire->ire_zoneid == ALL_ZONES ||
-			    ire->ire_type == IRE_LOCAL) {
+			    ire->ire_zoneid == ALL_ZONES) {
+				IRE_REFHOLD(ire);
+				rw_exit(&irb_ptr->irb_lock);
+				return (ire);
+			}
+
+			if (ire->ire_type == IRE_LOCAL) {
+				if (ip_restrict_interzone_loopback &&
+				    !ire_local_ok_across_zones(ire, zoneid,
+				    &addr, tsl))
+					continue;
+
 				IRE_REFHOLD(ire);
 				rw_exit(&irb_ptr->irb_lock);
 				return (ire);
--- a/usr/src/uts/common/inet/ip/ip_ndp.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip_ndp.c	Thu Sep 14 18:05:27 2006 -0700
@@ -56,6 +56,7 @@
 #include <inet/mib2.h>
 #include <inet/nd.h>
 #include <inet/ip.h>
+#include <inet/ip_impl.h>
 #include <inet/ip_if.h>
 #include <inet/ip_ire.h>
 #include <inet/ip_rts.h>
@@ -1023,37 +1024,6 @@
 }
 
 /*
- * Prepend the zoneid using an ipsec_out_t for later use by functions like
- * ip_rput_v6() after neighbor discovery has taken place.  If the message
- * block already has a M_CTL at the front of it, then simply set the zoneid
- * appropriately.
- */
-static mblk_t *
-ndp_prepend_zone(mblk_t *mp, zoneid_t zoneid)
-{
-	mblk_t		*first_mp;
-	ipsec_out_t	*io;
-
-	ASSERT(zoneid != ALL_ZONES);
-	if (mp->b_datap->db_type == M_CTL) {
-		io = (ipsec_out_t *)mp->b_rptr;
-		ASSERT(io->ipsec_out_type == IPSEC_OUT);
-		io->ipsec_out_zoneid = zoneid;
-		return (mp);
-	}
-
-	first_mp = ipsec_alloc_ipsec_out();
-	if (first_mp == NULL)
-		return (NULL);
-	io = (ipsec_out_t *)first_mp->b_rptr;
-	/* This is not a secure packet */
-	io->ipsec_out_secure = B_FALSE;
-	io->ipsec_out_zoneid = zoneid;
-	first_mp->b_cont = mp;
-	return (first_mp);
-}
-
-/*
  * Process resolve requests.  Handles both mapped entries
  * as well as cases that needs to be send out on the wire.
  * Lookup a NCE for a given IRE.  Regardless of whether one exists
@@ -1111,7 +1081,7 @@
 			NCE_REFRELE(nce);
 			return (0);
 		}
-		mp_nce = ndp_prepend_zone(mp, zoneid);
+		mp_nce = ip_prepend_zoneid(mp, zoneid);
 		if (mp_nce == NULL) {
 			/* The caller will free mp */
 			mutex_exit(&nce->nce_lock);
@@ -1139,7 +1109,7 @@
 		/* Resolution in progress just queue the packet */
 		mutex_enter(&nce->nce_lock);
 		if (nce->nce_state == ND_INCOMPLETE) {
-			mp_nce = ndp_prepend_zone(mp, zoneid);
+			mp_nce = ip_prepend_zoneid(mp, zoneid);
 			if (mp_nce == NULL) {
 				err = ENOMEM;
 			} else {
@@ -3092,7 +3062,7 @@
 		 */
 		(void) ip_hdr_complete_v6((ip6_t *)mp->b_rptr, zoneid);
 		icmp_unreachable_v6(nce->nce_ill->ill_wq, first_mp,
-		    ICMP6_DST_UNREACH_ADDR, B_FALSE, B_FALSE);
+		    ICMP6_DST_UNREACH_ADDR, B_FALSE, B_FALSE, zoneid);
 		mp = nxt_mp;
 	}
 }
@@ -3674,7 +3644,7 @@
 		(void) ip_hdr_complete((ipha_t *)mp->b_rptr, zoneid);
 		ip3dbg(("arp_resolv_failed: Calling icmp_unreachable\n"));
 		icmp_unreachable(nce->nce_ill->ill_wq, first_mp,
-		    ICMP_HOST_UNREACHABLE);
+		    ICMP_HOST_UNREACHABLE, zoneid);
 		mp = nxt_mp;
 	}
 }
--- a/usr/src/uts/common/inet/ip/ip_rts.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/ip_rts.c	Thu Sep 14 18:05:27 2006 -0700
@@ -280,11 +280,13 @@
 	tsol_gcgrp_t	*gcgrp = NULL;
 	tsol_gc_t	*gc = NULL;
 	ts_label_t	*tsl = NULL;
+	zoneid_t	zoneid;
 
 	ip1dbg(("ip_rts_request: mp is %x\n", DB_TYPE(mp)));
 
 	ASSERT(CONN_Q(q));
 	connp = Q_TO_CONN(q);
+	zoneid = connp->conn_zoneid;
 
 	ASSERT(mp->b_cont != NULL);
 	/* ioc_mp holds mp */
@@ -771,12 +773,25 @@
 		case AF_INET:
 			if (net_mask == IP_HOST_MASK) {
 				ire = ire_ctable_lookup(dst_addr, gw_addr,
-				    IRE_LOCAL | IRE_LOOPBACK, NULL, ALL_ZONES,
+				    IRE_LOCAL | IRE_LOOPBACK, NULL, zoneid,
 				    tsl, match_flags_local);
+				/*
+				 * If we found an IRE_LOCAL, make sure
+				 * it is one that would be used by this
+				 * zone to send packets.
+				 */
+				if (ire != NULL &&
+				    ire->ire_type == IRE_LOCAL &&
+				    ip_restrict_interzone_loopback &&
+				    !ire_local_ok_across_zones(ire,
+				    zoneid, &dst_addr, tsl)) {
+					ire_refrele(ire);
+					ire = NULL;
+				}
 			}
 			if (ire == NULL) {
 				ire = ire_ftable_lookup(dst_addr, net_mask,
-				    gw_addr, 0, ipif, &sire, ALL_ZONES, 0,
+				    gw_addr, 0, ipif, &sire, zoneid, 0,
 				    tsl, match_flags);
 			}
 			break;
@@ -784,12 +799,25 @@
 			if (IN6_ARE_ADDR_EQUAL(&net_mask_v6, &ipv6_all_ones)) {
 				ire = ire_ctable_lookup_v6(&dst_addr_v6,
 				    &gw_addr_v6, IRE_LOCAL | IRE_LOOPBACK, NULL,
-				    ALL_ZONES, tsl, match_flags_local);
+				    zoneid, tsl, match_flags_local);
+				/*
+				 * If we found an IRE_LOCAL, make sure
+				 * it is one that would be used by this
+				 * zone to send packets.
+				 */
+				if (ire != NULL &&
+				    ire->ire_type == IRE_LOCAL &&
+				    ip_restrict_interzone_loopback &&
+				    !ire_local_ok_across_zones(ire,
+				    zoneid, (void *)&dst_addr_v6, tsl)) {
+					ire_refrele(ire);
+					ire = NULL;
+				}
 			}
 			if (ire == NULL) {
 				ire = ire_ftable_lookup_v6(&dst_addr_v6,
 				    &net_mask_v6, &gw_addr_v6, 0, ipif, &sire,
-				    ALL_ZONES, 0, tsl, match_flags);
+				    zoneid, 0, tsl, match_flags);
 			}
 			break;
 		}
--- a/usr/src/uts/common/inet/ip/spd.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip/spd.c	Thu Sep 14 18:05:27 2006 -0700
@@ -3973,7 +3973,7 @@
  */
 mblk_t *
 ip_wput_attach_policy(mblk_t *ipsec_mp, ipha_t *ipha, ip6_t *ip6h, ire_t *ire,
-    conn_t *connp, boolean_t unspec_src)
+    conn_t *connp, boolean_t unspec_src, zoneid_t zoneid)
 {
 	mblk_t *mp;
 	ipsec_out_t *io = NULL;
@@ -4150,17 +4150,10 @@
 	io->ipsec_out_ill_index = ill_index;
 	io->ipsec_out_dontroute = conn_dontroutex;
 	io->ipsec_out_multicast_loop = conn_multicast_loopx;
-	/*
-	 * When conn is non-NULL, the zoneid is set by ipsec_init_ipsec_out().
-	 * Otherwise set the zoneid based on the ire.
-	 */
-	if (connp == NULL) {
-		zoneid_t zoneid = ire->ire_zoneid;
-
-		if (zoneid == ALL_ZONES)
-			zoneid = GLOBAL_ZONEID;
-		io->ipsec_out_zoneid = zoneid;
-	}
+
+	if (zoneid == ALL_ZONES)
+		zoneid = GLOBAL_ZONEID;
+	io->ipsec_out_zoneid = zoneid;
 	return (ipsec_mp);
 }
 
--- a/usr/src/uts/common/inet/ip6.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip6.h	Thu Sep 14 18:05:27 2006 -0700
@@ -347,9 +347,9 @@
 extern char	*inet_ntop(int, const void *, char *, int);
 extern int	inet_pton(int, char *, void *);
 extern void	icmp_time_exceeded_v6(queue_t *, mblk_t *, uint8_t,
-    boolean_t, boolean_t);
+    boolean_t, boolean_t, zoneid_t);
 extern void	icmp_unreachable_v6(queue_t *, mblk_t *, uint8_t,
-    boolean_t, boolean_t);
+    boolean_t, boolean_t, zoneid_t);
 extern void	icmp_inbound_error_fanout_v6(queue_t *, mblk_t *, ip6_t *,
     icmp6_t *, ill_t *, boolean_t, zoneid_t);
 extern boolean_t conn_wantpacket_v6(conn_t *, ill_t *, ip6_t *, int, zoneid_t);
--- a/usr/src/uts/common/inet/ip_if.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip_if.h	Thu Sep 14 18:05:27 2006 -0700
@@ -210,6 +210,8 @@
     mblk_t *, ipsq_func_t, int *);
 extern	ipif_t	*ipif_lookup_addr_v6(const in6_addr_t *, ill_t *, zoneid_t,
     queue_t *, mblk_t *, ipsq_func_t, int *);
+extern	zoneid_t ipif_lookup_addr_zoneid(ipaddr_t, ill_t *);
+extern	zoneid_t ipif_lookup_addr_zoneid_v6(const in6_addr_t *, ill_t *);
 extern	ipif_t	*ipif_lookup_group(ipaddr_t, zoneid_t);
 extern	ipif_t	*ipif_lookup_group_v6(const in6_addr_t *, zoneid_t);
 extern  ipif_t	*ipif_lookup_interface(ipaddr_t, ipaddr_t,
--- a/usr/src/uts/common/inet/ip_impl.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip_impl.h	Thu Sep 14 18:05:27 2006 -0700
@@ -491,6 +491,7 @@
 
 extern int	ip_wput_frag_mdt_min;
 extern boolean_t ip_can_frag_mdt(mblk_t *, ssize_t, ssize_t);
+extern mblk_t   *ip_prepend_zoneid(mblk_t *, zoneid_t);
 
 #endif	/* _KERNEL */
 
--- a/usr/src/uts/common/inet/ip_ire.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ip_ire.h	Thu Sep 14 18:05:27 2006 -0700
@@ -315,6 +315,10 @@
 extern	ire_t	*ire_ihandle_lookup_offlink(ire_t *, ire_t *);
 extern	ire_t	*ire_ihandle_lookup_offlink_v6(ire_t *, ire_t *);
 
+extern	boolean_t	ire_local_same_ill_group(ire_t *, ire_t *);
+extern	boolean_t	ire_local_ok_across_zones(ire_t *, zoneid_t, void *,
+			    const struct ts_label_s *tsl);
+
 extern	ire_t 	*ire_lookup_local(zoneid_t);
 extern	ire_t 	*ire_lookup_local_v6(zoneid_t);
 
--- a/usr/src/uts/common/inet/ipsec_impl.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/ipsec_impl.h	Thu Sep 14 18:05:27 2006 -0700
@@ -588,9 +588,9 @@
 extern void ipsec_actvec_free(ipsec_act_t *, uint_t);
 extern mblk_t *ipsec_construct_inverse_acquire(sadb_msg_t *, sadb_ext_t **);
 extern mblk_t *ip_wput_attach_policy(mblk_t *, ipha_t *, ip6_t *, ire_t *,
-    conn_t *, boolean_t);
+    conn_t *, boolean_t, zoneid_t);
 extern mblk_t	*ip_wput_ire_parse_ipsec_out(mblk_t *, ipha_t *, ip6_t *,
-    ire_t *, conn_t *, boolean_t);
+    ire_t *, conn_t *, boolean_t, zoneid_t);
 extern ipsec_policy_t *ipsec_find_policy(int, conn_t *,
     struct ipsec_out_s *, ipsec_selector_t *);
 extern ipsid_t *ipsid_lookup(int, char *);
--- a/usr/src/uts/common/inet/tcp.h	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/tcp.h	Thu Sep 14 18:05:27 2006 -0700
@@ -558,7 +558,8 @@
 extern void 	tcp_free(tcp_t *tcp);
 extern void	tcp_ddi_init(void);
 extern void	tcp_ddi_destroy(void);
-extern void	tcp_xmit_listeners_reset(mblk_t *mp, uint_t ip_hdr_len);
+extern void	tcp_xmit_listeners_reset(mblk_t *mp, uint_t ip_hdr_len,
+		    zoneid_t zoneid);
 extern void	tcp_conn_request(void *arg, mblk_t *mp, void *arg2);
 extern void	tcp_conn_request_unbound(void *arg, mblk_t *mp, void *arg2);
 extern void 	tcp_input(void *arg, mblk_t *mp, void *arg2);
--- a/usr/src/uts/common/inet/tcp/tcp.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/tcp/tcp.c	Thu Sep 14 18:05:27 2006 -0700
@@ -924,7 +924,8 @@
 static void	tcp_ack_timer(void *arg);
 static mblk_t	*tcp_ack_mp(tcp_t *tcp);
 static void	tcp_xmit_early_reset(char *str, mblk_t *mp,
-		    uint32_t seq, uint32_t ack, int ctl, uint_t ip_hdr_len);
+		    uint32_t seq, uint32_t ack, int ctl, uint_t ip_hdr_len,
+		    zoneid_t zoneid);
 static void	tcp_xmit_ctl(char *str, tcp_t *tcp, uint32_t seq,
 		    uint32_t ack, int ctl);
 static tcp_hsp_t *tcp_hsp_lookup(ipaddr_t addr);
@@ -21432,7 +21433,7 @@
  */
 static void
 tcp_xmit_early_reset(char *str, mblk_t *mp, uint32_t seq,
-    uint32_t ack, int ctl, uint_t ip_hdr_len)
+    uint32_t ack, int ctl, uint_t ip_hdr_len, zoneid_t zoneid)
 {
 	ipha_t		*ipha = NULL;
 	ip6_t		*ip6h = NULL;
@@ -21449,6 +21450,7 @@
 	queue_t		*q = tcp_g_q;
 	tcp_t		*tcp = Q_TO_TCP(q);
 	cred_t		*cr;
+	mblk_t		*nmp;
 
 	if (!tcp_send_rst_chk()) {
 		tcp_rst_unsent++;
@@ -21610,6 +21612,16 @@
 			return;
 		}
 	}
+	if (zoneid == ALL_ZONES)
+		zoneid = GLOBAL_ZONEID;
+
+	/* Add the zoneid so ip_output routes it properly */
+	if ((nmp = ip_prepend_zoneid(ipsec_mp, zoneid)) == NULL) {
+		freemsg(ipsec_mp);
+		return;
+	}
+	ipsec_mp = nmp;
+
 	/*
 	 * NOTE:  one might consider tracing a TCP packet here, but
 	 * this function has no active TCP state and no tcp structure
@@ -21750,7 +21762,7 @@
  * RST.
  */
 void
-tcp_xmit_listeners_reset(mblk_t *mp, uint_t ip_hdr_len)
+tcp_xmit_listeners_reset(mblk_t *mp, uint_t ip_hdr_len, zoneid_t zoneid)
 {
 	uchar_t		*rptr;
 	uint32_t	seg_len;
@@ -21829,7 +21841,7 @@
 		freemsg(ipsec_mp);
 	} else if (flags & TH_ACK) {
 		tcp_xmit_early_reset("no tcp, reset",
-		    ipsec_mp, seg_ack, 0, TH_RST, ip_hdr_len);
+		    ipsec_mp, seg_ack, 0, TH_RST, ip_hdr_len, zoneid);
 	} else {
 		if (flags & TH_SYN) {
 			seg_len++;
@@ -21848,7 +21860,7 @@
 
 		tcp_xmit_early_reset("no tcp, reset/ack",
 		    ipsec_mp, 0, seg_seq + seg_len,
-		    TH_RST | TH_ACK, ip_hdr_len);
+		    TH_RST | TH_ACK, ip_hdr_len, zoneid);
 	}
 }
 
--- a/usr/src/uts/common/inet/udp/udp.c	Thu Sep 14 15:51:02 2006 -0700
+++ b/usr/src/uts/common/inet/udp/udp.c	Thu Sep 14 18:05:27 2006 -0700
@@ -7922,7 +7922,7 @@
 	case TI_GETPEERNAME:
 		break;
 	default:
-		ip_output(Q_TO_CONN(q), mp, q, IP_WPUT);
+		ip_output(udp->udp_connp, mp, q, IP_WPUT);
 		return;
 	}