JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Programming Interfaces Guide     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

1.  Memory and CPU Management

2.  Session Description Protocol API

3.  Process Scheduler

4.  Locality Group APIs

5.  Input/Output Interfaces

6.  Interprocess Communication

7.  Socket Interfaces

8.  Programming With XTI and TLI

9.  Packet Filtering Hooks

Packet Filtering Hooks Interfaces

Packet Filtering Hooks Kernel Functions

Packet Filtering Hooks Data Types

Using the Packet Filtering Hooks Interfaces

IP Instances

Protocol Registration

Event Registration

The Packet Hook

Packet Filtering Hooks Example

10.  Transport Selection and Name-to-Address Mapping

11.  Real-time Programming and Administration

12.  The Oracle Solaris ABI and ABI Tools

A.  UNIX Domain Sockets

Index

Packet Filtering Hooks Example

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 9-1 Packet Filtering Hooks Example 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) {

                snprintf(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 9-2 net_inject Example 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 below:
 * 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));
}