/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/pattr.h>
#include <sys/dlpi.h>
#include <sys/atomic.h>
#include <sys/sunddi.h>
#include <sys/socket.h>
#include <sys/neti.h>
#include <sys/sdt.h>
#include <sys/cmn_err.h>

#include <netinet/in.h>
#include <inet/ipsec_impl.h>
#include <inet/common.h>
#include <inet/mib2.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ip_if.h>
#include <inet/ip_ire.h>
#include <inet/ip_impl.h>
#include <inet/ip_ndp.h>
#include <inet/ipclassifier.h>
#include <inet/ipp_common.h>
#include <inet/ip_ftable.h>

/*
 * IPv4 netinfo entry point declarations.
 */
static int 		ip_getifname(net_handle_t, phy_if_t, char *,
			    const size_t);
static int 		ip_getmtu(net_handle_t, phy_if_t, lif_if_t);
static int 		ip_getpmtuenabled(net_handle_t);
static int 		ip_getlifaddr(net_handle_t, phy_if_t, lif_if_t,
			    size_t, net_ifaddr_t [], void *);
static int		ip_getlifzone(net_handle_t, phy_if_t, lif_if_t,
			    zoneid_t *);
static int		ip_getlifflags(net_handle_t, phy_if_t, lif_if_t,
			    uint64_t *);
static phy_if_t		ip_phygetnext(net_handle_t, phy_if_t);
static phy_if_t 	ip_phylookup(net_handle_t, const char *);
static lif_if_t 	ip_lifgetnext(net_handle_t, phy_if_t, lif_if_t);
static int 		ip_inject(net_handle_t, inject_t, net_inject_t *);
static phy_if_t 	ip_routeto(net_handle_t, struct sockaddr *,
			    struct sockaddr *);
static int 		ip_ispartialchecksum(net_handle_t, mblk_t *);
static int 		ip_isvalidchecksum(net_handle_t, mblk_t *);

static int 		ipv6_getifname(net_handle_t, phy_if_t, char *,
			    const size_t);
static int 		ipv6_getmtu(net_handle_t, phy_if_t, lif_if_t);
static int 		ipv6_getlifaddr(net_handle_t, phy_if_t, lif_if_t,
			    size_t, net_ifaddr_t [], void *);
static int		ipv6_getlifzone(net_handle_t, phy_if_t, lif_if_t,
			    zoneid_t *);
static int		ipv6_getlifflags(net_handle_t, phy_if_t, lif_if_t,
			    uint64_t *);
static phy_if_t 	ipv6_phygetnext(net_handle_t, phy_if_t);
static phy_if_t 	ipv6_phylookup(net_handle_t, const char *);
static lif_if_t 	ipv6_lifgetnext(net_handle_t, phy_if_t, lif_if_t);
static int 		ipv6_inject(net_handle_t, inject_t, net_inject_t *);
static phy_if_t 	ipv6_routeto(net_handle_t, struct sockaddr *,
			    struct sockaddr *);
static int 		ipv6_isvalidchecksum(net_handle_t, mblk_t *);

static int 		net_no_getmtu(net_handle_t, phy_if_t, lif_if_t);
static int 		net_no_getpmtuenabled(net_handle_t);
static lif_if_t 	net_no_lifgetnext(net_handle_t, phy_if_t, lif_if_t);
static int 		net_no_inject(net_handle_t, inject_t, net_inject_t *);
static phy_if_t 	net_no_routeto(net_handle_t, struct sockaddr *,
			    struct sockaddr *);
static int 		net_no_ispartialchecksum(net_handle_t, mblk_t *);
static int 		net_no_getlifaddr(net_handle_t, phy_if_t, lif_if_t,
			    size_t, net_ifaddr_t [], void *);
static int		net_no_getlifzone(net_handle_t, phy_if_t, lif_if_t,
			    zoneid_t *);
static int		net_no_getlifflags(net_handle_t, phy_if_t, lif_if_t,
			    uint64_t *);

/* Netinfo private functions */
static	int		ip_getifname_impl(phy_if_t, char *,
			    const size_t, boolean_t, ip_stack_t *);
static	int		ip_getmtu_impl(phy_if_t, lif_if_t, boolean_t,
			    ip_stack_t *);
static	phy_if_t	ip_phylookup_impl(const char *, boolean_t,
			    ip_stack_t *);
static	lif_if_t	ip_lifgetnext_impl(phy_if_t, lif_if_t, boolean_t,
			    ip_stack_t *);
static	int		ip_inject_impl(inject_t, net_inject_t *, boolean_t,
			    ip_stack_t *);
static	int		ip_getifaddr_type(sa_family_t, ipif_t *, lif_if_t,
			    void *);
static	phy_if_t	ip_routeto_impl(struct sockaddr *, struct sockaddr *,
			    ip_stack_t *);
static	int		ip_getlifaddr_impl(sa_family_t, phy_if_t, lif_if_t,
			    size_t, net_ifaddr_t [], struct sockaddr *,
			    ip_stack_t *);
static	void		ip_ni_queue_in_func(void *);
static	void		ip_ni_queue_out_func(void *);
static	void		ip_ni_queue_func_impl(injection_t *,  boolean_t);

static net_protocol_t ipv4info = {
	NETINFO_VERSION,
	NHF_INET,
	ip_getifname,
	ip_getmtu,
	ip_getpmtuenabled,
	ip_getlifaddr,
	ip_getlifzone,
	ip_getlifflags,
	ip_phygetnext,
	ip_phylookup,
	ip_lifgetnext,
	ip_inject,
	ip_routeto,
	ip_ispartialchecksum,
	ip_isvalidchecksum
};


static net_protocol_t ipv6info = {
	NETINFO_VERSION,
	NHF_INET6,
	ipv6_getifname,
	ipv6_getmtu,
	ip_getpmtuenabled,
	ipv6_getlifaddr,
	ipv6_getlifzone,
	ipv6_getlifflags,
	ipv6_phygetnext,
	ipv6_phylookup,
	ipv6_lifgetnext,
	ipv6_inject,
	ipv6_routeto,
	ip_ispartialchecksum,
	ipv6_isvalidchecksum
};

static net_protocol_t arp_netinfo = {
	NETINFO_VERSION,
	NHF_ARP,
	ip_getifname,
	net_no_getmtu,
	net_no_getpmtuenabled,
	net_no_getlifaddr,
	net_no_getlifzone,
	net_no_getlifflags,
	ip_phygetnext,
	ip_phylookup,
	net_no_lifgetnext,
	net_no_inject,
	net_no_routeto,
	net_no_ispartialchecksum,
	ip_isvalidchecksum
};

/*
 * The taskq eventq_queue_in is used to process the upside inject messages.
 * The taskq eventq_queue_out is used to process the downside inject messages.
 * The taskq eventq_queue_nic is used to process the nic event messages.
 */
static ddi_taskq_t 	*eventq_queue_in = NULL;
static ddi_taskq_t 	*eventq_queue_out = NULL;
ddi_taskq_t 	*eventq_queue_nic = NULL;

/*
 * Initialize queues for inject.
 */
void
ip_net_g_init()
{
	if (eventq_queue_out == NULL) {
		eventq_queue_out = ddi_taskq_create(NULL,
		    "IP_INJECT_QUEUE_OUT", 1, TASKQ_DEFAULTPRI, 0);

		if (eventq_queue_out == NULL)
			cmn_err(CE_NOTE, "ipv4_net_init: "
			    "ddi_taskq_create failed for IP_INJECT_QUEUE_OUT");
	}

	if (eventq_queue_in == NULL) {
		eventq_queue_in = ddi_taskq_create(NULL,
		    "IP_INJECT_QUEUE_IN", 1, TASKQ_DEFAULTPRI, 0);

		if (eventq_queue_in == NULL)
			cmn_err(CE_NOTE, "ipv4_net_init: "
			    "ddi_taskq_create failed for IP_INJECT_QUEUE_IN");
	}

	if (eventq_queue_nic == NULL) {
		eventq_queue_nic = ddi_taskq_create(NULL,
		    "IP_NIC_EVENT_QUEUE", 1, TASKQ_DEFAULTPRI, 0);

		if (eventq_queue_nic == NULL)
			cmn_err(CE_NOTE, "ipv4_net_init: "
			    "ddi_taskq_create failed for IP_NIC_EVENT_QUEUE");
	}
}

/*
 * Destroy inject queues
 */
void
ip_net_g_destroy()
{
	if (eventq_queue_nic != NULL) {
		ddi_taskq_destroy(eventq_queue_nic);
		eventq_queue_nic = NULL;
	}

	if (eventq_queue_in != NULL) {
		ddi_taskq_destroy(eventq_queue_in);
		eventq_queue_in = NULL;
	}

	if (eventq_queue_out != NULL) {
		ddi_taskq_destroy(eventq_queue_out);
		eventq_queue_out = NULL;
	}
}

/*
 * Register IPv4 and IPv6 netinfo functions and initialize queues for inject.
 */
void
ip_net_init(ip_stack_t *ipst, netstack_t *ns)
{
	netid_t id;

	id = net_getnetidbynetstackid(ns->netstack_stackid);
	ASSERT(id != -1);

	ipst->ips_ipv4_net_data = net_protocol_register(id, &ipv4info);
	ASSERT(ipst->ips_ipv4_net_data != NULL);

	ipst->ips_ipv6_net_data = net_protocol_register(id, &ipv6info);
	ASSERT(ipst->ips_ipv6_net_data != NULL);

	ipst->ips_arp_net_data = net_protocol_register(id, &arp_netinfo);
	ASSERT(ipst->ips_ipv6_net_data != NULL);
}


/*
 * Unregister IPv4 and IPv6 functions.
 */
void
ip_net_destroy(ip_stack_t *ipst)
{
	if (ipst->ips_ipv4_net_data != NULL) {
		if (net_protocol_unregister(ipst->ips_ipv4_net_data) == 0)
			ipst->ips_ipv4_net_data = NULL;
	}

	if (ipst->ips_ipv6_net_data != NULL) {
		if (net_protocol_unregister(ipst->ips_ipv6_net_data) == 0)
			ipst->ips_ipv6_net_data = NULL;
	}

	if (ipst->ips_arp_net_data != NULL) {
		if (net_protocol_unregister(ipst->ips_arp_net_data) == 0)
			ipst->ips_arp_net_data = NULL;
	}
}

/*
 * Initialize IPv4 hooks family the event
 */
void
ipv4_hook_init(ip_stack_t *ipst)
{
	HOOK_FAMILY_INIT(&ipst->ips_ipv4root, Hn_IPV4);
	if (net_family_register(ipst->ips_ipv4_net_data, &ipst->ips_ipv4root)
	    != 0) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_family_register failed for ipv4");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_physical_in_event, NH_PHYSICAL_IN);
	ipst->ips_ipv4firewall_physical_in = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_physical_in_event);
	if (ipst->ips_ipv4firewall_physical_in == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/physical_in");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_physical_out_event, NH_PHYSICAL_OUT);
	ipst->ips_ipv4firewall_physical_out = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_physical_out_event);
	if (ipst->ips_ipv4firewall_physical_out == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/physical_out");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_forwarding_event, NH_FORWARDING);
	ipst->ips_ipv4firewall_forwarding = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_forwarding_event);
	if (ipst->ips_ipv4firewall_forwarding == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/forwarding");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_loopback_in_event, NH_LOOPBACK_IN);
	ipst->ips_ipv4firewall_loopback_in = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_loopback_in_event);
	if (ipst->ips_ipv4firewall_loopback_in == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/loopback_in");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_loopback_out_event, NH_LOOPBACK_OUT);
	ipst->ips_ipv4firewall_loopback_out = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_loopback_out_event);
	if (ipst->ips_ipv4firewall_loopback_out == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/loopback_out");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_nic_events, NH_NIC_EVENTS);
	ipst->ips_ip4_nic_events.he_flags = HOOK_RDONLY;
	ipst->ips_ipv4nicevents = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_nic_events);
	if (ipst->ips_ipv4nicevents == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/nic_events");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip4_observe, NH_OBSERVE);
	ipst->ips_ip4_observe.he_flags = HOOK_RDONLY;
	ipst->ips_ipv4observing = net_event_register(
	    ipst->ips_ipv4_net_data, &ipst->ips_ip4_observe);
	if (ipst->ips_ipv4observing == NULL) {
		cmn_err(CE_NOTE, "ipv4_hook_init: "
		    "net_event_register failed for ipv4/observe");
	}

}

void
ipv4_hook_shutdown(ip_stack_t *ipst)
{
	if (ipst->ips_ipv4firewall_forwarding != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_forwarding_event);
	}

	if (ipst->ips_ipv4firewall_physical_in != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_physical_in_event);
	}

	if (ipst->ips_ipv4firewall_physical_out != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_physical_out_event);
	}

	if (ipst->ips_ipv4firewall_loopback_in != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_loopback_in_event);
	}

	if (ipst->ips_ipv4firewall_loopback_out != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_loopback_out_event);
	}

	if (ipst->ips_ipv4nicevents != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_nic_events);
	}

	if (ipst->ips_ipv4observing != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_observe);
	}

	(void) net_family_shutdown(ipst->ips_ipv4_net_data,
	    &ipst->ips_ipv4root);
}

void
ipv4_hook_destroy(ip_stack_t *ipst)
{
	if (ipst->ips_ipv4firewall_forwarding != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_forwarding_event) == 0)
			ipst->ips_ipv4firewall_forwarding = NULL;
	}

	if (ipst->ips_ipv4firewall_physical_in != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_physical_in_event) == 0)
			ipst->ips_ipv4firewall_physical_in = NULL;
	}

	if (ipst->ips_ipv4firewall_physical_out != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_physical_out_event) == 0)
			ipst->ips_ipv4firewall_physical_out = NULL;
	}

	if (ipst->ips_ipv4firewall_loopback_in != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_loopback_in_event) == 0)
			ipst->ips_ipv4firewall_loopback_in = NULL;
	}

	if (ipst->ips_ipv4firewall_loopback_out != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_loopback_out_event) == 0)
			ipst->ips_ipv4firewall_loopback_out = NULL;
	}

	if (ipst->ips_ipv4nicevents != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_nic_events) == 0)
			ipst->ips_ipv4nicevents = NULL;
	}

	if (ipst->ips_ipv4observing != NULL) {
		if (net_event_unregister(ipst->ips_ipv4_net_data,
		    &ipst->ips_ip4_observe) == 0)
			ipst->ips_ipv4observing = NULL;
	}

	(void) net_family_unregister(ipst->ips_ipv4_net_data,
	    &ipst->ips_ipv4root);
}

/*
 * Initialize IPv6 hooks family and event
 */
void
ipv6_hook_init(ip_stack_t *ipst)
{

	HOOK_FAMILY_INIT(&ipst->ips_ipv6root, Hn_IPV6);
	if (net_family_register(ipst->ips_ipv6_net_data, &ipst->ips_ipv6root)
	    != 0) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_family_register failed for ipv6");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_physical_in_event, NH_PHYSICAL_IN);
	ipst->ips_ipv6firewall_physical_in = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_physical_in_event);
	if (ipst->ips_ipv6firewall_physical_in == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/physical_in");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_physical_out_event, NH_PHYSICAL_OUT);
	ipst->ips_ipv6firewall_physical_out = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_physical_out_event);
	if (ipst->ips_ipv6firewall_physical_out == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/physical_out");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_forwarding_event, NH_FORWARDING);
	ipst->ips_ipv6firewall_forwarding = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_forwarding_event);
	if (ipst->ips_ipv6firewall_forwarding == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/forwarding");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_loopback_in_event, NH_LOOPBACK_IN);
	ipst->ips_ipv6firewall_loopback_in = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_loopback_in_event);
	if (ipst->ips_ipv6firewall_loopback_in == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/loopback_in");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_loopback_out_event, NH_LOOPBACK_OUT);
	ipst->ips_ipv6firewall_loopback_out = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_loopback_out_event);
	if (ipst->ips_ipv6firewall_loopback_out == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/loopback_out");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_nic_events, NH_NIC_EVENTS);
	ipst->ips_ip6_nic_events.he_flags = HOOK_RDONLY;
	ipst->ips_ipv6nicevents = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_nic_events);
	if (ipst->ips_ipv6nicevents == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/nic_events");
	}

	HOOK_EVENT_INIT(&ipst->ips_ip6_observe, NH_OBSERVE);
	ipst->ips_ip6_observe.he_flags = HOOK_RDONLY;
	ipst->ips_ipv6observing = net_event_register(
	    ipst->ips_ipv6_net_data, &ipst->ips_ip6_observe);
	if (ipst->ips_ipv6observing == NULL) {
		cmn_err(CE_NOTE, "ipv6_hook_init: "
		    "net_event_register failed for ipv6/observe");
	}
}

void
ipv6_hook_shutdown(ip_stack_t *ipst)
{
	if (ipst->ips_ipv6firewall_forwarding != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_forwarding_event);
	}

	if (ipst->ips_ipv6firewall_physical_in != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_physical_in_event);
	}

	if (ipst->ips_ipv6firewall_physical_out != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_physical_out_event);
	}

	if (ipst->ips_ipv6firewall_loopback_in != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_loopback_in_event);
	}

	if (ipst->ips_ipv6firewall_loopback_out != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_loopback_out_event);
	}

	if (ipst->ips_ipv6nicevents != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_nic_events);
	}

	if (ipst->ips_ipv6observing != NULL) {
		(void) net_event_shutdown(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_observe);
	}

	(void) net_family_shutdown(ipst->ips_ipv6_net_data,
	    &ipst->ips_ipv6root);
}

void
ipv6_hook_destroy(ip_stack_t *ipst)
{
	if (ipst->ips_ipv6firewall_forwarding != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_forwarding_event) == 0)
			ipst->ips_ipv6firewall_forwarding = NULL;
	}

	if (ipst->ips_ipv6firewall_physical_in != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_physical_in_event) == 0)
			ipst->ips_ipv6firewall_physical_in = NULL;
	}

	if (ipst->ips_ipv6firewall_physical_out != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_physical_out_event) == 0)
			ipst->ips_ipv6firewall_physical_out = NULL;
	}

	if (ipst->ips_ipv6firewall_loopback_in != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_loopback_in_event) == 0)
			ipst->ips_ipv6firewall_loopback_in = NULL;
	}

	if (ipst->ips_ipv6firewall_loopback_out != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_loopback_out_event) == 0)
			ipst->ips_ipv6firewall_loopback_out = NULL;
	}

	if (ipst->ips_ipv6nicevents != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_nic_events) == 0)
			ipst->ips_ipv6nicevents = NULL;
	}

	if (ipst->ips_ipv6observing != NULL) {
		if (net_event_unregister(ipst->ips_ipv6_net_data,
		    &ipst->ips_ip6_observe) == 0)
			ipst->ips_ipv6observing = NULL;
	}

	(void) net_family_unregister(ipst->ips_ipv6_net_data,
	    &ipst->ips_ipv6root);
}

/*
 * Determine the name of an IPv4 interface
 */
static int
ip_getifname(net_handle_t neti, phy_if_t phy_ifdata, char *buffer,
    const size_t buflen)
{
	return (ip_getifname_impl(phy_ifdata, buffer, buflen, B_FALSE,
	    neti->netd_stack->nts_netstack->netstack_ip));
}

/*
 * Determine the name of an IPv6 interface
 */
static int
ipv6_getifname(net_handle_t neti, phy_if_t phy_ifdata, char *buffer,
    const size_t buflen)
{
	return (ip_getifname_impl(phy_ifdata, buffer, buflen, B_TRUE,
	    neti->netd_stack->nts_netstack->netstack_ip));
}

/*
 * Shared implementation to determine the name of a given network interface
 */
/* ARGSUSED */
static int
ip_getifname_impl(phy_if_t phy_ifdata,
    char *buffer, const size_t buflen, boolean_t isv6, ip_stack_t *ipst)
{
	ill_t *ill;

	ASSERT(buffer != NULL);

	ill = ill_lookup_on_ifindex((uint_t)phy_ifdata, isv6, ipst);
	if (ill == NULL)
		return (1);

	(void) strlcpy(buffer, ill->ill_name, buflen);
	ill_refrele(ill);
	return (0);
}

/*
 * Determine the MTU of an IPv4 network interface
 */
static int
ip_getmtu(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_getmtu_impl(phy_ifdata, ifdata, B_FALSE, ns->netstack_ip));
}

/*
 * Determine the MTU of an IPv6 network interface
 */
static int
ipv6_getmtu(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_getmtu_impl(phy_ifdata, ifdata, B_TRUE, ns->netstack_ip));
}

/*
 * Shared implementation to determine the MTU of a network interface
 */
/* ARGSUSED */
static int
ip_getmtu_impl(phy_if_t phy_ifdata, lif_if_t ifdata, boolean_t isv6,
    ip_stack_t *ipst)
{
	lif_if_t ipifid;
	ipif_t *ipif;
	int mtu;

	ipifid = UNMAP_IPIF_ID(ifdata);

	ipif = ipif_getby_indexes((uint_t)phy_ifdata, (uint_t)ipifid,
	    isv6, ipst);
	if (ipif == NULL)
		return (0);

	mtu = ipif->ipif_ill->ill_mtu;
	ipif_refrele(ipif);

	if (mtu == 0) {
		ill_t *ill;

		if ((ill = ill_lookup_on_ifindex((uint_t)phy_ifdata, isv6,
		    ipst)) == NULL) {
			return (0);
		}
		mtu = ill->ill_mtu;
		ill_refrele(ill);
	}

	return (mtu);
}

/*
 * Determine if path MTU discovery is enabled for IP
 */
static int
ip_getpmtuenabled(net_handle_t neti)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ns->netstack_ip->ips_ip_path_mtu_discovery);
}

/*
 * Get next interface from the current list of IPv4 physical network interfaces
 */
static phy_if_t
ip_phygetnext(net_handle_t neti, phy_if_t phy_ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ill_get_next_ifindex(phy_ifdata, B_FALSE, ns->netstack_ip));
}

/*
 * Get next interface from the current list of IPv6 physical network interfaces
 */
static phy_if_t
ipv6_phygetnext(net_handle_t neti, phy_if_t phy_ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ill_get_next_ifindex(phy_ifdata, B_TRUE, ns->netstack_ip));
}

/*
 * Determine if a network interface name exists for IPv4
 */
static phy_if_t
ip_phylookup(net_handle_t neti, const char *name)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_phylookup_impl(name, B_FALSE, ns->netstack_ip));
}

/*
 * Determine if a network interface name exists for IPv6
 */
static phy_if_t
ipv6_phylookup(net_handle_t neti, const char *name)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_phylookup_impl(name, B_TRUE, ns->netstack_ip));
}

/*
 * Implement looking up an ill_t based on the name supplied and matching
 * it up with either IPv4 or IPv6.  ill_get_ifindex_by_name() is not used
 * because it does not match on the address family in addition to the name.
 */
static phy_if_t
ip_phylookup_impl(const char *name, boolean_t isv6, ip_stack_t *ipst)
{
	phy_if_t phy;
	ill_t *ill;

	ill = ill_lookup_on_name((char *)name, B_FALSE, isv6, NULL, ipst);
	if (ill == NULL)
		return (0);

	phy = ill->ill_phyint->phyint_ifindex;

	ill_refrele(ill);

	return (phy);
}

/*
 * Get next interface from the current list of IPv4 logical network interfaces
 */
static lif_if_t
ip_lifgetnext(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_lifgetnext_impl(phy_ifdata, ifdata, B_FALSE,
	    ns->netstack_ip));
}

/*
 * Get next interface from the current list of IPv6 logical network interfaces
 */
static lif_if_t
ipv6_lifgetnext(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_lifgetnext_impl(phy_ifdata, ifdata, B_TRUE,
	    ns->netstack_ip));
}

/*
 * Shared implementation to get next interface from the current list of
 * logical network interfaces
 */
static lif_if_t
ip_lifgetnext_impl(phy_if_t phy_ifdata, lif_if_t ifdata, boolean_t isv6,
    ip_stack_t *ipst)
{
	lif_if_t newidx, oldidx;
	boolean_t nextok;
	ipif_t *ipif;
	ill_t *ill;

	ill = ill_lookup_on_ifindex(phy_ifdata, isv6, ipst);
	if (ill == NULL)
		return (0);

	if (ifdata != 0) {
		oldidx = UNMAP_IPIF_ID(ifdata);
		nextok = B_FALSE;
	} else {
		oldidx = 0;
		nextok = B_TRUE;
	}

	mutex_enter(&ill->ill_lock);
	if (ill->ill_state_flags & ILL_CONDEMNED) {
		mutex_exit(&ill->ill_lock);
		ill_refrele(ill);
		return (0);
	}

	/*
	 * It's safe to iterate the ill_ipif list when holding an ill_lock.
	 * And it's also safe to access ipif_id without ipif refhold.
	 * See the field access rules in ip.h.
	 */
	for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next) {
		if (!IPIF_CAN_LOOKUP(ipif))
			continue;
		if (nextok) {
			ipif_refhold_locked(ipif);
			break;
		} else if (oldidx == ipif->ipif_id) {
			nextok = B_TRUE;
		}
	}

	mutex_exit(&ill->ill_lock);
	ill_refrele(ill);

	if (ipif == NULL)
		return (0);

	newidx = ipif->ipif_id;
	ipif_refrele(ipif);

	return (MAP_IPIF_ID(newidx));
}

/*
 * Inject an IPv4 packet to or from an interface
 */
static int
ip_inject(net_handle_t neti, inject_t style, net_inject_t *packet)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_inject_impl(style, packet, B_FALSE, ns->netstack_ip));
}


/*
 * Inject an IPv6 packet to or from an interface
 */
static int
ipv6_inject(net_handle_t neti, inject_t style, net_inject_t *packet)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	return (ip_inject_impl(style, packet, B_TRUE, ns->netstack_ip));
}

/*
 * Shared implementation to inject a packet to or from an interface
 * Return value:
 *   0: successful
 *  -1: memory allocation failed
 *   1: other errors
 */
static int
ip_inject_impl(inject_t style, net_inject_t *packet, boolean_t isv6,
    ip_stack_t *ipst)
{
	ddi_taskq_t *tq = NULL;
	void (* func)(void *);
	injection_t *inject;
	mblk_t *mp;

	ASSERT(packet != NULL);
	ASSERT(packet->ni_packet != NULL);
	ASSERT(packet->ni_packet->b_datap->db_type == M_DATA);

	switch (style) {
	case NI_QUEUE_IN:
		inject = kmem_alloc(sizeof (*inject), KM_NOSLEEP);
		if (inject == NULL)
			return (-1);
		inject->inj_data = *packet;
		inject->inj_isv6 = isv6;
		/*
		 * deliver up into the kernel, immitating its reception by a
		 * network interface, add to list and schedule timeout
		 */
		func = ip_ni_queue_in_func;
		tq = eventq_queue_in;
		break;

	case NI_QUEUE_OUT:
		inject = kmem_alloc(sizeof (*inject), KM_NOSLEEP);
		if (inject == NULL)
			return (-1);
		inject->inj_data = *packet;
		inject->inj_isv6 = isv6;
		/*
		 * deliver out of the kernel, as if it were being sent via a
		 * raw socket so that IPFilter will see it again, add to list
		 * and schedule timeout
		 */
		func = ip_ni_queue_out_func;
		tq = eventq_queue_out;
		break;

	case NI_DIRECT_OUT: {
		struct sockaddr *sock;

		mp = packet->ni_packet;

		sock = (struct sockaddr *)&packet->ni_addr;
		/*
		 * ipfil_sendpkt was provided by surya to ease the
		 * problems associated with sending out a packet.
		 */
		switch (ipfil_sendpkt(sock, mp, packet->ni_physical,
		    netstackid_to_zoneid(
		    ipst->ips_netstack->netstack_stackid))) {
		case 0 :
		case EINPROGRESS:
			return (0);
		case ECOMM :
		case ENONET :
			return (1);
		default :
			return (1);
		}
		/* NOTREACHED */
	}
	default:
		freemsg(packet->ni_packet);
		return (1);
	}

	ASSERT(tq != NULL);

	inject->inj_ptr = ipst;
	if (ddi_taskq_dispatch(tq, func, (void *)inject,
	    DDI_SLEEP) == DDI_FAILURE) {
		ip2dbg(("ip_inject:  ddi_taskq_dispatch failed\n"));
		freemsg(packet->ni_packet);
		return (1);
	}
	return (0);
}

/*
 * Find the interface used for traffic to a given IPv4 address
 */
static phy_if_t
ip_routeto(net_handle_t neti, struct sockaddr *address, struct sockaddr *next)
{
	netstack_t *ns;

	ASSERT(address != NULL);

	if (address->sa_family != AF_INET)
		return (0);

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);

	return (ip_routeto_impl(address, next, ns->netstack_ip));
}

/*
 * Find the interface used for traffic to a given IPv6 address
 */
static phy_if_t
ipv6_routeto(net_handle_t neti, struct sockaddr *address, struct sockaddr *next)
{
	netstack_t *ns;

	ASSERT(address != NULL);

	if (address->sa_family != AF_INET6)
		return (0);

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);

	return (ip_routeto_impl(address, next, ns->netstack_ip));
}


/*
 * Find the interface used for traffic to an address.
 * For lint reasons, next/next6/sin/sin6 are all declared and assigned
 * a value at the top.  The alternative would end up with two bunches
 * of assignments, with each bunch setting half to NULL.
 */
static phy_if_t
ip_routeto_impl(struct sockaddr *address, struct sockaddr *nexthop,
    ip_stack_t *ipst)
{
	struct sockaddr_in6 *next6 = (struct sockaddr_in6 *)nexthop;
	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)address;
	struct sockaddr_in *next = (struct sockaddr_in *)nexthop;
	struct sockaddr_in *sin = (struct sockaddr_in *)address;
	ire_t *ire;
	ire_t *nexthop_ire;
	phy_if_t phy_if;
	zoneid_t zoneid;

	zoneid = netstackid_to_zoneid(ipst->ips_netstack->netstack_stackid);

	if (address->sa_family == AF_INET6) {
		ire = ire_route_recursive_v6(&sin6->sin6_addr, 0, NULL,
		    zoneid, NULL, MATCH_IRE_DSTONLY, IRR_ALLOCATE, 0, ipst,
		    NULL, NULL, NULL);
	} else {
		ire = ire_route_recursive_v4(sin->sin_addr.s_addr, 0, NULL,
		    zoneid, NULL, MATCH_IRE_DSTONLY, IRR_ALLOCATE, 0, ipst,
		    NULL, NULL, NULL);
	}
	ASSERT(ire != NULL);
	/*
	 * For some destinations, we have routes that are dead ends, so
	 * return to indicate that no physical interface can be used to
	 * reach the destination.
	 */
	if (ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
		ire_refrele(ire);
		return (NULL);
	}

	nexthop_ire = ire_nexthop(ire);
	if (nexthop_ire == NULL) {
		ire_refrele(ire);
		return (0);
	}
	if (nexthop_ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)) {
		ire_refrele(nexthop_ire);
		ire_refrele(ire);
		return (0);
	}

	ASSERT(nexthop_ire->ire_ill != NULL);

	if (nexthop != NULL) {
		if (address->sa_family == AF_INET6) {
			next6->sin6_addr = nexthop_ire->ire_addr_v6;
		} else {
			next->sin_addr.s_addr = nexthop_ire->ire_addr;
		}
	}

	phy_if = (phy_if_t)nexthop_ire->ire_ill->ill_phyint->phyint_ifindex;
	ire_refrele(ire);
	ire_refrele(nexthop_ire);

	return (phy_if);
}

/*
 * Determine if checksumming is being used for the given packet.
 *
 * Return value:
 *   NET_HCK_NONE: full checksum recalculation is required
 *   NET_HCK_L3_FULL: full layer 3 checksum
 *   NET_HCK_L4_FULL: full layer 4 checksum
 *   NET_HCK_L4_PART: partial layer 4 checksum
 */
/*ARGSUSED*/
static int
ip_ispartialchecksum(net_handle_t neti, mblk_t *mp)
{
	int ret = 0;

	ASSERT(mp != NULL);

	if ((DB_CKSUMFLAGS(mp) & HCK_FULLCKSUM) != 0) {
		ret |= (int)NET_HCK_L4_FULL;
		if ((DB_CKSUMFLAGS(mp) & HCK_IPV4_HDRCKSUM) != 0)
			ret |= (int)NET_HCK_L3_FULL;
	}
	if ((DB_CKSUMFLAGS(mp) & HCK_PARTIALCKSUM) != 0) {
		ret |= (int)NET_HCK_L4_PART;
		if ((DB_CKSUMFLAGS(mp) & HCK_IPV4_HDRCKSUM) != 0)
			ret |= (int)NET_HCK_L3_FULL;
	}

	return (ret);
}

/*
 * Return true or false, indicating whether the network and transport
 * headers are correct.  Use the capabilities flags and flags set in the
 * dblk_t to determine whether or not the checksum is valid.
 *
 * Return:
 *   0: the checksum was incorrect
 *   1: the original checksum was correct
 */
/*ARGSUSED*/
static int
ip_isvalidchecksum(net_handle_t neti, mblk_t *mp)
{
	unsigned char *wptr;
	ipha_t *ipha = (ipha_t *)mp->b_rptr;
	int hlen;
	int ret;

	ASSERT(mp != NULL);

	if (dohwcksum &&
	    ((DB_CKSUM16(mp) != 0xFFFF &&
	    (DB_CKSUMFLAGS(mp) & HCK_FULLCKSUM)) ||
	    (DB_CKSUMFLAGS(mp) & HCK_FULLCKSUM_OK)) &&
	    (DB_CKSUMFLAGS(mp) & HCK_IPV4_HDRCKSUM_OK))
		return (1);

	hlen = (ipha->ipha_version_and_hdr_length & 0x0F) << 2;

	/*
	 * Check that the mblk being passed in has enough data in it
	 * before blindly checking ip_cksum.
	 */
	if (msgdsize(mp) < hlen)
		return (0);

	if (mp->b_wptr < mp->b_rptr + hlen) {
		if (pullupmsg(mp, hlen) == 0)
			return (0);
		wptr = mp->b_wptr;
	} else {
		wptr = mp->b_wptr;
		mp->b_wptr = mp->b_rptr + hlen;
	}

	if (ipha->ipha_hdr_checksum == ip_cksum(mp, 0, ipha->ipha_hdr_checksum))
		ret = 1;
	else
		ret = 0;
	mp->b_wptr = wptr;

	return (ret);
}

/*
 * Unsupported with IPv6
 */
/*ARGSUSED*/
static int
ipv6_isvalidchecksum(net_handle_t neti, mblk_t *mp)
{
	return (-1);
}

/*
 * Determine the network addresses for an IPv4 interface
 */
static int
ip_getlifaddr(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    size_t nelem, net_ifaddr_t type[], void *storage)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_getlifaddr_impl(AF_INET, phy_ifdata, ifdata,
	    nelem, type, storage, ns->netstack_ip));
}

/*
 * Determine the network addresses for an IPv6 interface
 */
static int
ipv6_getlifaddr(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    size_t nelem, net_ifaddr_t type[], void *storage)
{
	netstack_t *ns;

	ns = neti->netd_stack->nts_netstack;
	ASSERT(ns != NULL);
	return (ip_getlifaddr_impl(AF_INET6, phy_ifdata, ifdata,
	    nelem, type, storage, ns->netstack_ip));
}

/*
 * Shared implementation to determine the network addresses for an interface
 */
/* ARGSUSED */
static int
ip_getlifaddr_impl(sa_family_t family, phy_if_t phy_ifdata,
    lif_if_t ifdata, size_t nelem, net_ifaddr_t type[],
    struct sockaddr *storage, ip_stack_t *ipst)
{
	struct sockaddr_in6 *sin6;
	struct sockaddr_in *sin;
	lif_if_t ipifid;
	ipif_t *ipif;
	int i;

	ASSERT(type != NULL);
	ASSERT(storage != NULL);

	ipifid = UNMAP_IPIF_ID(ifdata);

	if (family == AF_INET) {
		if ((ipif = ipif_getby_indexes((uint_t)phy_ifdata,
		    (uint_t)ipifid, B_FALSE, ipst)) == NULL)
			return (1);

		sin = (struct sockaddr_in *)storage;
		for (i = 0; i < nelem; i++, sin++) {
			if (ip_getifaddr_type(AF_INET, ipif, type[i],
			    &sin->sin_addr) < 0) {
				ip2dbg(("ip_getlifaddr_impl failed type %d\n",
				    type[i]));
				ipif_refrele(ipif);
				return (1);
			}
			sin->sin_family = AF_INET;
		}
	} else {
		if ((ipif = ipif_getby_indexes((uint_t)phy_ifdata,
		    (uint_t)ipifid, B_TRUE, ipst)) == NULL)
			return (1);

		sin6 = (struct sockaddr_in6 *)storage;
		for (i = 0; i < nelem; i++, sin6++) {
			if (ip_getifaddr_type(AF_INET6, ipif, type[i],
			    &sin6->sin6_addr) < 0) {
				ip2dbg(("ip_getlifaddr_impl failed type %d\n",
				    type[i]));
				ipif_refrele(ipif);
				return (1);
			}
			sin6->sin6_family = AF_INET6;
		}
	}
	ipif_refrele(ipif);
	return (0);
}

/*
 * ip_getlifaddr private function
 */
static int
ip_getifaddr_type(sa_family_t family, ipif_t *ill_ipif,
    lif_if_t type, void *storage)
{
	void *src_addr;
	int mem_size;

	ASSERT(ill_ipif != NULL);
	ASSERT(storage != NULL);

	if (family == AF_INET) {
		mem_size = sizeof (struct in_addr);

		switch (type) {
		case NA_ADDRESS:
			src_addr = &(ill_ipif->ipif_lcl_addr);
			break;
		case NA_PEER:
			src_addr = &(ill_ipif->ipif_pp_dst_addr);
			break;
		case NA_BROADCAST:
			src_addr = &(ill_ipif->ipif_brd_addr);
			break;
		case NA_NETMASK:
			src_addr = &(ill_ipif->ipif_net_mask);
			break;
		default:
			return (-1);
			/*NOTREACHED*/
		}
	} else {
		mem_size = sizeof (struct in6_addr);

		switch (type) {
		case NA_ADDRESS:
			src_addr = &(ill_ipif->ipif_v6lcl_addr);
			break;
		case NA_PEER:
			src_addr = &(ill_ipif->ipif_v6pp_dst_addr);
			break;
		case NA_BROADCAST:
			src_addr = &(ill_ipif->ipif_v6brd_addr);
			break;
		case NA_NETMASK:
			src_addr = &(ill_ipif->ipif_v6net_mask);
			break;
		default:
			return (-1);
			/*NOTREACHED*/
		}
	}

	(void) memcpy(storage, src_addr, mem_size);
	return (1);
}

/*
 * Shared implementation to determine the zoneid associated with an IPv4/IPv6
 * address
 */
static int
ip_getlifzone_impl(sa_family_t family, phy_if_t phy_ifdata, lif_if_t ifdata,
    ip_stack_t *ipst, zoneid_t *zoneid)
{
	ipif_t  *ipif;

	ipif = ipif_getby_indexes((uint_t)phy_ifdata,
	    UNMAP_IPIF_ID((uint_t)ifdata), (family == AF_INET6), ipst);
	if (ipif == NULL)
		return (-1);
	*zoneid = IP_REAL_ZONEID(ipif->ipif_zoneid, ipst);
	ipif_refrele(ipif);
	return (0);
}

/*
 * Determine the zoneid associated with an IPv4 address
 */
static int
ip_getlifzone(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    zoneid_t *zoneid)
{
	return (ip_getlifzone_impl(AF_INET, phy_ifdata, ifdata,
	    neti->netd_stack->nts_netstack->netstack_ip, zoneid));
}

/*
 * Determine the zoneid associated with an IPv6 address
 */
static int
ipv6_getlifzone(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    zoneid_t *zoneid)
{
	return (ip_getlifzone_impl(AF_INET6, phy_ifdata, ifdata,
	    neti->netd_stack->nts_netstack->netstack_ip, zoneid));
}

/*
 * The behaviour here mirrors that for the SIOCFLIFFLAGS ioctl where the
 * union of all of the relevant flags is returned.
 */
static int
ip_getlifflags_impl(sa_family_t family, phy_if_t phy_ifdata, lif_if_t ifdata,
    ip_stack_t *ipst, uint64_t *flags)
{
	phyint_t *phyi;
	ipif_t *ipif;
	ill_t *ill;

	ill = ill_lookup_on_ifindex(phy_ifdata, (family == AF_INET6), ipst);
	if (ill == NULL)
		return (-1);
	phyi = ill->ill_phyint;

	ipif = ipif_getby_indexes((uint_t)phy_ifdata,
	    UNMAP_IPIF_ID((uint_t)ifdata), (family == AF_INET6), ipst);
	if (ipif == NULL) {
		ill_refrele(ill);
		return (-1);
	}
	*flags = ipif->ipif_flags | ill->ill_flags | phyi->phyint_flags;
	ipif_refrele(ipif);
	ill_refrele(ill);
	return (0);
}

static int
ip_getlifflags(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    uint64_t *flags)
{
	return (ip_getlifflags_impl(AF_INET, phy_ifdata, ifdata,
	    neti->netd_stack->nts_netstack->netstack_ip, flags));
}

static int
ipv6_getlifflags(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    uint64_t *flags)
{
	return (ip_getlifflags_impl(AF_INET6, phy_ifdata, ifdata,
	    neti->netd_stack->nts_netstack->netstack_ip, flags));
}

/*
 * Deliver packet up into the kernel, immitating its reception by a
 * network interface.
 */
static void
ip_ni_queue_in_func(void *inject)
{
	ip_ni_queue_func_impl(inject, B_FALSE);
}

/*
 * Deliver out of the kernel, as if it were being sent via a
 * raw socket so that IPFilter will see it again.
 */
static void
ip_ni_queue_out_func(void *inject)
{
	ip_ni_queue_func_impl(inject, B_TRUE);
}

/*
 * Shared implementation for inject via ip_output and ip_input
 */
static void
ip_ni_queue_func_impl(injection_t *inject,  boolean_t out)
{
	net_inject_t *packet;
	ill_t *ill;
	ip_stack_t *ipst = (ip_stack_t *)inject->inj_ptr;
	ip_xmit_attr_t	ixas;

	ASSERT(inject != NULL);
	packet = &inject->inj_data;
	ASSERT(packet->ni_packet != NULL);

	if (out == 0) {
		ill = ill_lookup_on_ifindex((uint_t)packet->ni_physical,
		    inject->inj_isv6, ipst);

		if (ill == NULL) {
			kmem_free(inject, sizeof (*inject));
			return;
		}

		if (inject->inj_isv6) {
			ip_input_v6(ill, NULL, packet->ni_packet, NULL);
		} else {
			ip_input(ill, NULL, packet->ni_packet, NULL);
		}
		ill_refrele(ill);
	} else {
		bzero(&ixas, sizeof (ixas));
		ixas.ixa_ifindex = packet->ni_physical;
		ixas.ixa_ipst = ipst;
		if (inject->inj_isv6) {
			ixas.ixa_flags = IXAF_BASIC_SIMPLE_V6;
		} else {
			ixas.ixa_flags = IXAF_BASIC_SIMPLE_V4;
		}
		ixas.ixa_flags &= ~IXAF_VERIFY_SOURCE;
		(void) ip_output_simple(packet->ni_packet, &ixas);
		ixa_cleanup(&ixas);
	}

	kmem_free(inject, sizeof (*inject));
}

/*
 * taskq function for nic events.
 */
void
ip_ne_queue_func(void *arg)
{
	hook_event_token_t hr;
	hook_nic_event_int_t *info = (hook_nic_event_int_t *)arg;
	ip_stack_t *ipst;
	netstack_t *ns;

	ns = netstack_find_by_stackid(info->hnei_stackid);
	if (ns == NULL)
		goto done;

	ipst = ns->netstack_ip;
	if (ipst == NULL)
		goto done;

	hr = (info->hnei_event.hne_protocol == ipst->ips_ipv6_net_data) ?
	    ipst->ips_ipv6nicevents : ipst->ips_ipv4nicevents;
	(void) hook_run(info->hnei_event.hne_protocol->netd_hooks, hr,
	    (hook_data_t)&info->hnei_event);

done:
	if (ns != NULL)
		netstack_rele(ns);
	kmem_free(info->hnei_event.hne_data, info->hnei_event.hne_datalen);
	kmem_free(arg, sizeof (hook_nic_event_int_t));
}

/*
 * Initialize ARP hook family and events
 */
void
arp_hook_init(ip_stack_t *ipst)
{
	HOOK_FAMILY_INIT(&ipst->ips_arproot, Hn_ARP);
	if (net_family_register(ipst->ips_arp_net_data, &ipst->ips_arproot)
	    != 0) {
		cmn_err(CE_NOTE, "arp_hook_init"
		    "net_family_register failed for arp");
	}

	HOOK_EVENT_INIT(&ipst->ips_arp_physical_in_event, NH_PHYSICAL_IN);
	ipst->ips_arp_physical_in = net_event_register(ipst->ips_arp_net_data,
	    &ipst->ips_arp_physical_in_event);
	if (ipst->ips_arp_physical_in == NULL) {
		cmn_err(CE_NOTE, "arp_hook_init: "
		    "net_event_register failed for arp/physical_in");
	}

	HOOK_EVENT_INIT(&ipst->ips_arp_physical_out_event, NH_PHYSICAL_OUT);
	ipst->ips_arp_physical_out = net_event_register(ipst->ips_arp_net_data,
	    &ipst->ips_arp_physical_out_event);
	if (ipst->ips_arp_physical_out == NULL) {
		cmn_err(CE_NOTE, "arp_hook_init: "
		    "net_event_register failed for arp/physical_out");
	}

	HOOK_EVENT_INIT(&ipst->ips_arp_nic_events, NH_NIC_EVENTS);
	ipst->ips_arpnicevents = net_event_register(ipst->ips_arp_net_data,
	    &ipst->ips_arp_nic_events);
	if (ipst->ips_arpnicevents == NULL) {
		cmn_err(CE_NOTE, "arp_hook_init: "
		    "net_event_register failed for arp/nic_events");
	}
}

void
arp_hook_destroy(ip_stack_t *ipst)
{
	if (ipst->ips_arpnicevents != NULL) {
		if (net_event_unregister(ipst->ips_arp_net_data,
		    &ipst->ips_arp_nic_events) == 0)
			ipst->ips_arpnicevents = NULL;
	}

	if (ipst->ips_arp_physical_out != NULL) {
		if (net_event_unregister(ipst->ips_arp_net_data,
		    &ipst->ips_arp_physical_out_event) == 0)
			ipst->ips_arp_physical_out = NULL;
	}

	if (ipst->ips_arp_physical_in != NULL) {
		if (net_event_unregister(ipst->ips_arp_net_data,
		    &ipst->ips_arp_physical_in_event) == 0)
			ipst->ips_arp_physical_in = NULL;
	}

	(void) net_family_unregister(ipst->ips_arp_net_data,
	    &ipst->ips_arproot);
}

void
arp_hook_shutdown(ip_stack_t *ipst)
{
	if (ipst->ips_arp_physical_in != NULL) {
		(void) net_event_shutdown(ipst->ips_arp_net_data,
		    &ipst->ips_arp_physical_in_event);
	}
	if (ipst->ips_arp_physical_out != NULL) {
		(void) net_event_shutdown(ipst->ips_arp_net_data,
		    &ipst->ips_arp_physical_out_event);
	}
	if (ipst->ips_arpnicevents != NULL) {
		(void) net_event_shutdown(ipst->ips_arp_net_data,
		    &ipst->ips_arp_nic_events);
	}
}

/* netinfo routines for the unsupported cases */

/* ARGSUSED */
int
net_no_getmtu(net_handle_t handle, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	return (-1);
}

/* ARGSUSED */
static int
net_no_getpmtuenabled(net_handle_t neti)
{
	return (-1);
}

/* ARGSUSED */
static lif_if_t
net_no_lifgetnext(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata)
{
	return (-1);
}

/* ARGSUSED */
static int
net_no_inject(net_handle_t neti, inject_t style, net_inject_t *packet)
{
	return (-1);
}

/* ARGSUSED */
static phy_if_t
net_no_routeto(net_handle_t neti, struct sockaddr *address,
    struct sockaddr *next)
{
	return ((phy_if_t)-1);
}

/* ARGSUSED */
static int
net_no_ispartialchecksum(net_handle_t neti, mblk_t *mp)
{
	return (-1);
}

/* ARGSUSED */
static int
net_no_getlifaddr(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    size_t nelem, net_ifaddr_t type[], void *storage)
{
	return (-1);
}

/* ARGSUSED */
static int
net_no_getlifzone(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    zoneid_t *zoneid)
{
	return (-1);
}

/* ARGSUSED */
static int
net_no_getlifflags(net_handle_t neti, phy_if_t phy_ifdata, lif_if_t ifdata,
    uint64_t *flags)
{
	return (-1);
}