Go to main content

Oracle® Solaris 11.3 Programming Interfaces Guide

Exit Print View

Updated: April 2019
 
 

Packet Filtering Hooks Sample Program

Following is a complete example that can be compiled and loaded into the kernel.

Use the following commands to compile this code into a working kernel module on a 64-bit system:

# gcc -D_KERNEL -m64 -c full.c
# ld -dy -Nmisc/neti -Nmisc/hook -r full.o -o full
Example 46  Showing a Packet Filtering Hooks Program
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * This file is a test module written to test the netinfo APIs in Oracle Solaris 11.
 * It is being published to demonstrate how the APIs can be used.
 */
#include <sys/param.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include "neti.h"

/*
 * Module linkage information for the kernel.
 */
static struct modldrv modlmisc = {
        &mod_miscops,           /* drv_modops */
        "neti test module",     /* drv_linkinfo */
};

static struct modlinkage modlinkage = {
        MODREV_1,               /* ml_rev */
        &modlmisc,              /* ml_linkage */
        NULL
};

typedef struct scratch_s {
        int             sentinel_1;
        netid_t         id;
        int             sentinel_2;
        int             event_notify;
        int             sentinel_3;
        int             v4_event_notify;
        int             sentinel_4;
        int             v6_event_notify;
        int             sentinel_5;
        int             arp_event_notify;
        int             sentinel_6;
        int             v4_hook_notify;
        int             sentinel_7;
        int             v6_hook_notify;
        int             sentinel_8;
        int             arp_hook_notify;
        int             sentinel_9;
        hook_t          *v4_h_in;
        int             sentinel_10;
        hook_t          *v6_h_in;
        int             sentinel_11;
        hook_t          *arp_h_in;
        int             sentinel_12;
        net_handle_t    v4;
        int             sentinel_13;
        net_handle_t    v6;
        int             sentinel_14;
        net_handle_t    arp;
        int             sentinel_15;
} scratch_t;

#define MAX_RECALL_DOLOG        10000
char    recall_myname[10];
net_instance_t *recall_global;
int     recall_inited = 0;
int     recall_doing[MAX_RECALL_DOLOG];
int     recall_doidx = 0;
kmutex_t        recall_lock;
int     recall_continue = 1;
timeout_id_t    recall_timeout;
int     recall_steps = 0;
int     recall_alloced = 0;
void    *recall_alloclog[MAX_RECALL_DOLOG];
int     recall_freed = 0;
void    *recall_freelog[MAX_RECALL_DOLOG];

static int recall_init(void);
static void recall_fini(void);
static void *recall_create(const netid_t id);
static void recall_shutdown(const netid_t id, void *arg);
static void recall_destroy(const netid_t id, void *arg);
static int recall_newproto(hook_notify_cmd_t cmd, void *arg,
    const char *parent, const char *event, const char *hook);
static int recall_newevent(hook_notify_cmd_t cmd, void *arg,
    const char *parent, const char *event, const char *hook);
static int recall_newhook(hook_notify_cmd_t cmd, void *arg,
    const char *parent, const char *event, const char *hook);
static void recall_expire(void *arg);

static void recall_strfree(char *);
static char *recall_strdup(char *, int);

static void
recall_add_do(int mydo)
{
        mutex_enter(&recall_lock);
        recall_doing[recall_doidx] = mydo;
        recall_doidx++;
        recall_steps++;
        if ((recall_steps % 1000000) == 0)
                printf("stamp %d %d\n", recall_steps, recall_doidx);
        if (recall_doidx == MAX_RECALL_DOLOG)
                recall_doidx = 0;
        mutex_exit(&recall_lock);
}

static void *recall_alloc(size_t len, int wait)
{
        int i;

        mutex_enter(&recall_lock);
        i = recall_alloced++;
        if (recall_alloced == MAX_RECALL_DOLOG)
                recall_alloced = 0;
        mutex_exit(&recall_lock);

        recall_alloclog[i] = kmem_alloc(len, wait);
        return recall_alloclog[i];
}

static void recall_free(void *ptr, size_t len)
{
        int i;

        mutex_enter(&recall_lock);
        i = recall_freed++;
        if (recall_freed == MAX_RECALL_DOLOG)
                recall_freed = 0;
        mutex_exit(&recall_lock);

        recall_freelog[i] = ptr;
        kmem_free(ptr, len);
}

static void recall_assert(scratch_t *s)
{
        ASSERT(s->sentinel_1 == 0);
        ASSERT(s->sentinel_2 == 0);
        ASSERT(s->sentinel_3 == 0);
        ASSERT(s->sentinel_4 == 0);
        ASSERT(s->sentinel_5 == 0);
        ASSERT(s->sentinel_6 == 0);
        ASSERT(s->sentinel_7 == 0);
        ASSERT(s->sentinel_8 == 0);
        ASSERT(s->sentinel_9 == 0);
        ASSERT(s->sentinel_10 == 0);
        ASSERT(s->sentinel_11 == 0);
        ASSERT(s->sentinel_12 == 0);
        ASSERT(s->sentinel_13 == 0);
        ASSERT(s->sentinel_14 == 0);
        ASSERT(s->sentinel_15 == 0);
}

int
_init(void)
{
        int error;

        bzero(recall_doing, sizeof(recall_doing));
        mutex_init(&recall_lock, NULL, MUTEX_DRIVER, NULL);

        error = recall_init();
        if (error == DDI_SUCCESS) {
                error = mod_install(&modlinkage);
                if (error != 0)
                        recall_fini();
        }

        recall_timeout = timeout(recall_expire, NULL, drv_usectohz(500000));

        return (error);
}

int
_fini(void)
{
        int error;

        recall_continue = 0;
        if (recall_timeout != NULL) {
                untimeout(recall_timeout);
                recall_timeout = NULL;
        }
        error = mod_remove(&modlinkage);
        if (error == 0) {
                recall_fini();
                delay(drv_usectohz(500000));    /* .5 seconds */

                mutex_destroy(&recall_lock);

                ASSERT(recall_inited == 0);
        }

        return (error);
}

int
_info(struct modinfo *info)
{
        return(0);
}

static int
recall_init()
{
        recall_global = net_instance_alloc(NETINFO_VERSION);

        strcpy(recall_myname, "full_");
        bcopy(((char *)&recall_global) + 4, recall_myname + 5, 4);
        recall_myname[5] = (recall_myname[5] & 0x7f) | 0x20;
        recall_myname[6] = (recall_myname[6] & 0x7f) | 0x20;
        recall_myname[7] = (recall_myname[7] & 0x7f) | 0x20;
        recall_myname[8] = (recall_myname[8] & 0x7f) | 0x20;
        recall_myname[9] = '\0';

        recall_global->nin_create = recall_create;
        recall_global->nin_shutdown = recall_shutdown;
        recall_global->nin_destroy = recall_destroy;
        recall_global->nin_name = recall_myname;

        if (net_instance_register(recall_global) != 0)
                return (DDI_FAILURE);

        return (DDI_SUCCESS);
}

static void
recall_fini()
{

        if (recall_global != NULL) {
                net_instance_unregister(recall_global);
                net_instance_free(recall_global);
                recall_global = NULL;
        }
}

static void
recall_expire(void *arg)
{

        if (!recall_continue)
                return;

        recall_fini();

        if (!recall_continue)
                return;

        delay(drv_usectohz(5000));      /* .005 seconds */

        if (!recall_continue)
                return;

        if (recall_init() == DDI_SUCCESS)
                recall_timeout = timeout(recall_expire, NULL,
                    drv_usectohz(5000));        /* .005 seconds */
}

static void *
recall_create(const netid_t id)
{
        scratch_t *s = kmem_zalloc(sizeof(*s), KM_SLEEP);

        if (s == NULL)
                return (NULL);

        recall_inited++;

        s->id = id;

        net_instance_notify_register(id, recall_newproto, s);

        return s;
}

static void
recall_shutdown(const netid_t id, void *arg)
{
        scratch_t *s = arg;

        ASSERT(s != NULL);
        recall_add_do(__LINE__);
        net_instance_notify_unregister(id, recall_newproto);

        if (s->v4 != NULL) {
                if (s->v4_h_in != NULL) {
                        net_hook_unregister(s->v4, NH_PHYSICAL_IN,
                            s->v4_h_in);
                        recall_strfree(s->v4_h_in->h_name);
                        hook_free(s->v4_h_in);
                        s->v4_h_in = NULL;
                }
                if (net_protocol_notify_unregister(s->v4, recall_newevent))
                        cmn_err(CE_WARN,
                            "v4:net_protocol_notify_unregister(%p) failed",
                            s->v4);
                net_protocol_release(s->v4);
                s->v4 = NULL;
        }

        if (s->v6 != NULL) {
                if (s->v6_h_in != NULL) {
                        net_hook_unregister(s->v6, NH_PHYSICAL_IN,
                            s->v6_h_in);
                        recall_strfree(s->v6_h_in->h_name);
                        hook_free(s->v6_h_in);
                        s->v6_h_in = NULL;
                }
                if (net_protocol_notify_unregister(s->v6, recall_newevent))
                        cmn_err(CE_WARN,
                            "v6:net_protocol_notify_unregister(%p) failed",
                            s->v6);
                net_protocol_release(s->v6);
                s->v6 = NULL;
        }

        if (s->arp != NULL) {
                if (s->arp_h_in != NULL) {
                        net_hook_unregister(s->arp, NH_PHYSICAL_IN,
                            s->arp_h_in);
                        recall_strfree(s->arp_h_in->h_name);
                        hook_free(s->arp_h_in);
                        s->arp_h_in = NULL;
                }
                if (net_protocol_notify_unregister(s->arp, recall_newevent))
                        cmn_err(CE_WARN,
                            "arp:net_protocol_notify_unregister(%p) failed",
                            s->arp);
                net_protocol_release(s->arp);
                s->arp = NULL;
        }
}

static void
recall_destroy(const netid_t id, void *arg)
{
        scratch_t *s = arg;

        ASSERT(s != NULL);

        recall_assert(s);

        ASSERT(s->v4 == NULL);
        ASSERT(s->v6 == NULL);
        ASSERT(s->arp == NULL);
        ASSERT(s->v4_h_in == NULL);
        ASSERT(s->v6_h_in == NULL);
        ASSERT(s->arp_h_in == NULL);
        kmem_free(s, sizeof(*s));

        ASSERT(recall_inited > 0);
        recall_inited--;
}

static int
recall_newproto(hook_notify_cmd_t cmd, void *arg, const char *parent,
    const char *event, const char *hook)
{
        scratch_t *s = arg;

        s->event_notify++;

        recall_assert(s);

        switch (cmd) {
        case HN_REGISTER :
                if (strcmp(parent, NHF_INET) == 0) {
                        s->v4 = net_protocol_lookup(s->id, parent);
                        net_protocol_notify_register(s->v4, recall_newevent, s);
                } else if (strcmp(parent, NHF_INET6) == 0) {
                        s->v6 = net_protocol_lookup(s->id, parent);
                        net_protocol_notify_register(s->v6, recall_newevent, s);
                } else if (strcmp(parent, NHF_ARP) == 0) {
                        s->arp = net_protocol_lookup(s->id, parent);
                        net_protocol_notify_register(s->arp,recall_newevent, s);
                }
                break;

        case HN_UNREGISTER :
        case HN_NONE :
                break;
        }

        return 0;
}

static int
recall_do_event(hook_event_token_t tok, hook_data_t data, void *ctx)
{
        scratch_t *s = ctx;

        recall_assert(s);

        return (0);
}

static int
recall_newevent(hook_notify_cmd_t cmd, void *arg, const char *parent,
    const char *event, const char *hook)
{
        scratch_t *s = arg;
        char buffer[32];
        hook_t *h;

        recall_assert(s);

        if (strcmp(event, NH_PHYSICAL_IN) == 0) {

                sn

printf(buffer, sizeof(buffer), 

"%s_%s_%s", recall_myname, parent, event);
                h = hook_alloc(HOOK_VERSION);
                h->h_hint = HH_NONE;
                h->h_arg = s;
                h->h_name = recall_strdup(buffer, KM_SLEEP);
                h->h_func = recall_do_event;
        } else {
                h = NULL;
        }

        if (strcmp(parent, NHF_INET) == 0) {
                s->v4_event_notify++;
                if (h != NULL) {
                        s->v4_h_in = h;
                        net_hook_register(s->v4, (char *)event, h);
                }
                net_event_notify_register(s->v4, (char *)event,
                    recall_newhook, s);

        } else if (strcmp(parent, NHF_INET6) == 0) {
                s->v6_event_notify++;
                if (h != NULL) {
                        s->v6_h_in = h;
                        net_hook_register(s->v6, (char *)event, h);
                }
                net_event_notify_register(s->v6, (char *)event,
                    recall_newhook, s);

        } else if (strcmp(parent, NHF_ARP) == 0) {
                s->arp_event_notify++;
                if (h != NULL) {
                        s->arp_h_in = h;
                        net_hook_register(s->arp, (char *)event, h);
                }
                net_event_notify_register(s->arp, (char *)event,
                    recall_newhook, s);
        }
        recall_assert(s);

        return (0);
}

static int
recall_newhook(hook_notify_cmd_t cmd, void *arg, const char *parent,
    const char *event, const char *hook)
{
        scratch_t *s = arg;

        recall_assert(s);

        if (strcmp(parent, NHF_INET) == 0) {
                s->v4_hook_notify++;

        } else if (strcmp(parent, NHF_INET6) == 0) {
                s->v6_hook_notify++;

        } else if (strcmp(parent, NHF_ARP) == 0) {
                s->arp_hook_notify++;
        }
        recall_assert(s);

        return (0);
}

static void recall_strfree(char *str)
{
        int len;

        if (str != NULL) {
                len = strlen(str);
                recall_free(str, len + 1);
        }
}

static char* recall_strdup(char *str, int wait)
{
        char *newstr;
        int len;

        len = strlen(str);
        newstr = recall_alloc(len, wait);
        if (newstr != NULL)
                strcpy(newstr, str);

        return (newstr);
}
Example 47  Showing a net_inject() Program
* Copyright (c) 2012, Oracle and/or its affiliates.
 All rights reserved.
 */

* PAMP driver - Ping Amplifier enables Solaris to send two ICMP echo 
* responses for every ICMP request.
* This example provides a test module of the Oracle Solaris PF hooks 
* (netinfo(9f)) API.This example discovers ICMP echo 
* implementation by intercepting inbound packets using 
* physical-in` event hook. 
* If the intercepted packet happens to be a ICMPv4 echo request, 
* the module will generate a corresponding ICMP echo response 
* which will then be sent to the network interface card using 
* the net_inject(9f) function. The original ICMPv4 echo request will be
* allowed to enter the the IP stack so that the request can be 
* processed by the destination IP stack. 
* The destination stack in turn will send its own ICMPv4 echo response. 
* Therefore there will be two ICMPv4 echo responses for a single 
* ICMPv4 echo request.

*
* The following example code demonstrates two key functions of netinfo(9f) API:
*
* Packet Interception
*
* Packet Injection
*
* In order to be able to talk to netinfo(9f), the driver must allocate and
* register its own net_instance_t - `pamp_ninst`. This happens in the
* pamp_attach() function, which imlements `ddi_attach` driver operation.The
* net_instance_t registers three callbacks with netinfo(9f) module:
* 	_create
*	_shutdown
*	_destroy
* The netinfo(9f) command uses these functions to request the driver to
* create, shutdown, or destroy the driver context bound to a particular IP instance.
* This will enable the driver to handle packets for every IP stack found in
* the Oracle Solaris kernel. For purposes of this example, the driver is always
* implicitly bound to every IP instance.
*/
 
/* Use the following makefile to build the driver::
/* Begin Makefile */
ALL = pamp_drv pamp_drv.conf

pamp_drv = pamp_drv.o

pamp_drv.conf: pamp_drv
echo 'name="pamp_drv" parent="pseudo" instance=0;' > pamp_drv.conf

pamp_drv: pamp_drv.o
ld -dy -r -Ndrv/ip -Nmisc/neti -Nmsic/hook -o pamp_drv pamp_drv.o
pamp_drv.o: pamp_drv.c
cc -m64 -xmodel=kernel -D_KERNEL -c -o $@ $<

install:
cp pamp_drv /usr/kernel/drv/`isainfo -k`/pamp_drv
cp pamp_drv.conf /usr/kernel/drv/pamp_drv.conf

uninstall:
rm -rf /usr/kernel/drv/`isainfo -k`/pamp_drv
rm -rf /usr/kernel/drv/pamp_drv.conf

clean:
	rm -f pamp_drv.o pamp_drv pamp_drv.conf

*End Makefile */

 *
* The Makefile shown above will build a pamp_drv driver binary 
* and pamp_drv.conf file for driver configuration. If you are 
* building on a test machine, use `make install` to place 
* driver and configuration files in the specified location.
* Otherwise copy the pamp_drv binary and the pamp_drv.conf 
files to your test machine manually.
*
* Run the following command to load the driver to kernel:

	add_drv pam_drv
* Run the following command to unload the driver to kernel:

	rem_drv pamp_drv
*
* To check if your driver is working you need to use a snoop 
* and `ping` which will be running
* on a remote host. Start snoop on your network interface:

	snoop -d netX icmp

 * Run a ping on a remote host:

ping -ns <test.box>
* test.box refers to the system where the driver is installed.

*
* The snoop should show there are two ICMP echo replies for every ICMP echo
 * request. The expected output should be similar to the snoop output shown here:
 * 172.16.1.2 -> 172.16.1.100 ICMP Echo request (ID: 16652 Sequence number: 0)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 0)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 0)
 * 172.16.1.2 -> 172.16.1.100 ICMP Echo request (ID: 16652 Sequence number: 1)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 1)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 1)
 * 172.16.1.2 -> 172.16.1.100 ICMP Echo request (ID: 16652 Sequence number: 2)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 2)
 * 172.16.1.100 -> 172.16.1.2   ICMP Echo reply (ID: 16652 Sequence number: 2)
 */
#include <sys/atomic.h>
#include <sys/ksynch.h>
#include <sys/ddi.h>
#include <sys/modctl.h>
#include <sys/random.h>
#include <sys/sunddi.h>
#include <sys/stream.h>
#include <sys/devops.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/neti.h>
#include <sys/hook.h>
#include <sys/hook_event.h>
#include <sys/synch.h>
#include <inet/ip.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h
#include <netinet/ip_icmp.h>


/*
 * This is a context for the driver. The context is allocated by
 * pamp_nin_create() callback for every IP instance found in kernel.
 */
typedef struct pamp_ipstack 
{
	hook_t *pamp_phyin;
	int pamp_hook_ok;
	net_handle_t	pamp_ipv4;
} pamp_ipstack_t;
static kmutex_t	pamp_stcksmx;
/*
 * The netinstance, which passes driver callbacks to netinfo module.
 */
static net_instance_t	*pamp_ninst = NULL;
/*
 * Solaris kernel driver APIs. 
 */
static int pamp_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int pamp_attach(dev_info_t *, ddi_attach_cmd_t);
static int pamp_detach(dev_info_t *, ddi_detach_cmd_t);static dev_info_t	*pamp_dev_info = NULL;
/*
 * Driver does not support any device operations.
 */

extern struct cb_ops no_cb_ops;

static struct dev_ops pamp_ops = {
	DEVO_REV,
	0,
	pamp_getinfo,
	nulldev,
	nulldev,
	pamp_attach,
	pamp_detach,
	nodev,
 &no_cb_ops,
	NULL,
	NULL,
	ddi_quiesce_not_needed,		/* quiesce */
};

static struct modldrv	pamp_module = {
&mod_driverops,
	"ECHO_1",
	&pamp_ops
};
static struct modlinkage pamp_modlink = {
	MODREV_1,
	&pamp_module,
	NULL
};

/*
 * Netinfo stack instance create/destroy/shutdown routines.
 */
static void *pamp_nin_create(const netid_t);
static void pamp_nin_destroy(const netid_t, void *);
static void pamp_nin_shutdown(const netid_t, void *);

/*
 * Callback to process intercepted packets delivered by hook event
 */
static int pamp_pkt_in(hook_event_token_t, hook_data_t, void *);

/*
 * Kernel driver getinfo operation
 */
static int
pamp_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void * arg, void **resultp)
{
	int	e;

	switch (cmd) {
		case DDI_INFO_DEVT2DEVINFO:
			*resultp = pamp_dev_info;
			e = DDI_SUCCESS;
			break;
		case DDI_INFO_DEVT2INSTANCE:
			*resultp = NULL;
			e = DDI_SUCCESS;
			break;
		default:
			e = DDI_FAILURE;
	}

	return (e);
}
/*
 * Kernel driver attach operation. The job of the driver is to create a net 
 * instance for our driver and register it with netinfo(9f)
 */
static int pamp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int	rc;
#define	RETURN(_x_)
	do	{
		mutex_exit(&pamp_stcksmx);
		return (_x_);
	} while (0)

	/*
	 * Fail for all commands except DDI_ATTACH.
	 */
	if (cmd != DDI_ATTACH) {
		return (DDI_FAILURE);
	}
	mutex_enter(&pamp_stcksmx);
	/*
	 * It is an error to apply attach operation on a driver which is already
	 * attached.
	 */
	if (pamp_ninst != NULL) {
		RETURN(DDI_FAILURE);
	}
	/*
	 * At most one driver instance is allowed (instance 0).
	 */
	if (ddi_get_instance(dip) != 0) {
		RETURN(DDI_FAILURE);
	}

	rc = ddi_create_minor_node(dip, "pamp", S_IFCHR, 0, DDI_PSEUDO, 0);
	if (rc != DDI_SUCCESS) {
		ddi_remove_minor_node(dip, NULL);
		RETURN(DDI_FAILURE);
	}
	
	/*
	 * Create and register pamp net instance.  Note we are assigning
	 * callbacks _create, _destroy, _shutdown. These callbacks will ask
	 * our driver to create/destroy/shutdown our IP driver instances.
	 */
	pamp_ninst = net_instance_alloc(NETINFO_VERSION);
	if (pamp_ninst == NULL) {
		ddi_remove_minor_node(dip, NULL);
		RETURN(DDI_FAILURE);
	}

	pamp_ninst->nin_name = "pamp";
	pamp_ninst->nin_create = pamp_nin_create;
	pamp_ninst->nin_destroy = pamp_nin_destroy;
	pamp_ninst->nin_shutdown = pamp_nin_shutdown;
	pamp_dev_info = dip;
	mutex_exit(&pamp_stcksmx);

	/* 
	 * Although it is not shown in the following example, it is
	 * recommended that all mutexes/exclusive locks be released before *
	 * calling net_instance_register(9F) to avoid a recursive lock
	 * entry.  As soon as pamp_ninst is registered, the
	 * net_instance_register(9f) will call pamp_nin_create() callback.
	 * The callback will run in the same context as the one in which
	 * pamp_attach() is running. If pamp_nin_create() grabs the same
	 * lock held already by pamp_attach(), then such a lock is being
	 * operated on recursively.
	 */
	(void) net_instance_register(pamp_ninst);

	return (DDI_SUCCESS);
#undef	RETURN
}

/*
 * The detach function will unregister and destroy our driver netinstance. The same rules
 * for exclusive locks/mutexes introduced for attach operation apply to detach.
 * The netinfo will take care to call the shutdown()/destroy() callbacks for
 * every IP stack instance.
 */
static int
pamp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	pamp_ipstack_t	*pamp_ipstack;
	net_instance_t	*ninst = NULL;

	/*
	 * It is an error to apply detach operation on driver, when another
	 * detach operation is running (in progress), or when detach operation
	 * is complete (pamp_ninst).
	 */
	mutex_enter(&pamp_stcksmx);
	if (pamp_ninst == NULL) {
		mutex_exit(&pamp_stcksmx);
		return (DDI_FAILURE);
	}

	ninst = pamp_ninst;
	pamp_ninst = NULL;
	mutex_exit(&pamp_stcksmx);

	/*
	 * Calling net_instance_unregister(9f) will invoke pamp_nin_destroy()
	 * for every pamp_ipstack instance created so far.  Therefore it is advisable
	 * to not hold any mutexes, because it might get grabbed by pamp_nin_destroy() function.
	 */
	net_instance_unregister(ninst);
	net_instance_free(ninst);

	(void) ddi_get_instance(dip);
	ddi_remove_minor_node(dip, NULL);


	return (DDI_SUCCESS);
}

/*
 * Netinfo callback, which is supposed to create an IP stack context for our
 * ICMP echo server.
 *
 * NOTE: NULL return value is not interpreted as a failure here. The
 * pamp_nin_shutdown()/pamp_nin_destroy() will receive NULL pointer for IP stack
 * instance with given `netid` id.
 *
 */
static void *
pamp_nin_create(const netid_t netid)
{
	pamp_ipstack_t	*pamp_ipstack;

	pamp_ipstack = (pamp_ipstack_t *)kmem_zalloc(
	    sizeof (pamp_ipstack_t), KM_NOSLEEP);

	if (pamp_ipstack == NULL) {
		return (NULL);
	}

	HOOK_INIT(pamp_ipstack->pamp_phyin, pamp_pkt_in, "pkt_in",
	    pamp_ipstack);

	pamp_ipstack->pamp_ipv4 = net_protocol_lookup(netid, NHF_INET);
	if (pamp_ipstack->pamp_ipv4 == NULL) {
		kmem_free(pamp_ipstack, sizeof (pamp_ipstack_t));
		return (NULL);
	}

	pamp_ipstack->pamp_hook_ok = net_hook_register(
	    pamp_ipstack->pamp_ipv4, NH_PHYSICAL_IN, pamp_ipstack->pamp_phyin);
	if (pamp_ipstack->pamp_hook_ok != 0) {
		net_protocol_release(pamp_ipstack->pamp_ipv4);
		hook_free(pamp_ipstack->pamp_phyin);
		kmem_free(pamp_ipstack, sizeof (pamp_ipstack_t));
		return (NULL);
	}

	return (pamp_ipstack);
}

/*
 * This event is delivered right before the particular stack instance is
 * destroyed.
 */
static void
pamp_nin_shutdown(const netid_t netid, void *stack)
{
	return;
}

/*
 * Important to note here that the netinfo(9f) module ensures that no
 * no pamp_pkt_in() is "running" when the stack it is bound to is being destroyed.
 */


static void
pamp_nin_destroy(const netid_t netid, void *stack)
{
	pamp_ipstack_t	*pamp_ipstack = (pamp_ipstack_t *)stack;

	/*
	 * Remember stack can be NULL! The pamp_nin_create() function returns
	 * NULL on failure. The return value of pamp_nin_create() function will
	 * be `kept` in netinfo module as a driver context for particular IP
	 * instance. As soon as the instance is destroyed the NULL value
	 * will appear here in pamp_nin_destroy(). Same applies to
	 * pamp_nin_shutdown(). Therefore our driver must be able to handle
	 * NULL here.
	 */
	if (pamp_ipstack == NULL)
		return;

	/*
	 * If driver has managed to initialize packet hook, then it has to be 
	 * unhooked here.
	 */
	if (pamp_ipstack->pamp_hook_ok != -1) {
		(void) net_hook_unregister(pamp_ipstack->pamp_ipv4,
		    NH_PHYSICAL_IN, pamp_ipstack->pamp_phyin);
		hook_free(pamp_ipstack->pamp_phyin);
		(void) net_protocol_release(pamp_ipstack->pamp_ipv4);
	}

	kmem_free(pamp_ipstack, sizeof (pamp_ipstack_t));
}

/*
 * Packet hook handler
 *
 * Function receives intercepted IPv4 packets coming from NIC to IP stack.  If
 * inbound packet is ICMP ehco request, then function will generate ICMP echo
 * response and use net_inject() to send it to network.  Function will also let
 * ICMP echo request in, so it will be still processed by destination IP stack,
 * which should also generate its own ICMP echo response. The snoop should show
 * you there will be two ICMP echo responses leaving the system where the pamp 
 * driver is installed
 */

static int
pamp_pkt_in(hook_event_token_t ev, hook_data_t info, void *arg)
{
	hook_pkt_event_t	*hpe = (hook_pkt_event_t *)info;
	phy_if_t		phyif;
	struct ip		*ip;

	/*
	 * Since our pamp_pkt_in callback is hooked to PHYSICAL_IN hook pkt.
	 * event only, the physical interface index will always be passed as
	 * hpe_ifp member.
	 *
	 * If our hook processes PHYSICAL_OUT hook pkt event, then
	 * the physical interface index will be passed as hpe_ofp member.
	 */
 	phyif = hpe->hpe_ifp;

	ip = hpe->hpe_hdr;
	if (ip->ip_p == IPPROTO_ICMP) {
		mblk_t	*mb;

		/*
		 * All packets are copied/placed into a continuous buffer to make
		 * parsing easier.
		 */
		if ((mb = msgpullup(hpe->hpe_mb, -1)) != NULL) {
			struct icmp	*icmp;
			pamp_ipstack_t	*pamp_ipstack = (pamp_ipstack_t *)arg;

			ip = (struct ip *)mb->b_rptr;
			icmp = (struct icmp *)(mb->b_rptr + IPH_HDR_LENGTH(ip));

			if (icmp->icmp_type == ICMP_ECHO) {
				struct in_addr	addr;
				uint32_t	sum;
				mblk_t	*echo_resp = copymsg(mb);
				net_inject_t	ninj;

				/*
				 * We need to make copy of packet, since we are
				 * going to turn it into ICMP echo response.
				 */
				if (echo_resp == NULL) {
					return (0);
				}
				ip = (struct ip *)echo_resp->b_rptr;
				addr = ip->ip_src;
				ip->ip_src = ip->ip_dst;
				ip->ip_dst = addr;
				icmp = (struct icmp *) (echo_resp->b_rptr + IPH_HDR_LENGTH(ip));
				icmp->icmp_type = ICMP_ECHO_REPLY;
				sum = ~ntohs(icmp->icmp_cksum) & 0xffff;
				sum += (ICMP_ECHO_REQUEST - ICMP_ECHO_REPLY);
				icmp->icmp_cksum =
				    htons(~((sum >> 16) + (sum & 0xffff)));

				/*
				 * Now we have assembled an ICMP response with
				 * correct chksum.  It's time to send it out.
				 * We have to initialize command for
				 * net_inject(9f) --  ninj.
				 */
				ninj.ni_packet = echo_resp;
				ninj.ni_physical = phyif;
				/*
				 * As we are going use NI_QUEUE_OUT to send
				 * our ICMP response, we don't need to set up
				 * .ni_addr, which is required for NI_DIRECT_OUT
				 * injection path only. In such case packet
				 * bypasses IP stack routing and is pushed
				 * directly to physical device queue. Therefore
				 * net_inject(9f) requires as to specify
				 * next-hop IP address.
				 *
				 * Using NI_QUEUE_OUT is more convenient for us
				 * since IP stack will take care of routing
				 * process and will find out `ni_addr`
				 * (next-hop) address on its own.
				 */
				(void) net_inject(pamp_ipstack->pamp_ipv4,
				    NI_QUEUE_OUT, &ninj);
			}
		}
	}

	/*
	 * 0 as return value will let packet in.
	 */
	return (0);
}

/*
 * Kernel module handling.
 */
int init()
{
	mutex_init(&pamp_stcksmx, "pamp_mutex", MUTEX_DRIVER, NULL);
	return (mod_install(&pamp_modlink));
}

int fini()
{
	int rv;

	rv = mod_remove(&pamp_modlink);
	return (rv);
}

int info(struct modinfo *modinfop)
{
	return (mod_info(&pamp_modlink, modinfop));
}