JavaScript is required to for searching.
跳过导航链接
退出打印视图
编程接口指南     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

1.  内存和 CPU 管理

2.  用于 Solaris Cluster 的远程共享内存 API

3.  会话描述协议 API

4.  进程调度程序

5.  地址组 API

6.  输入/输出接口

7.  进程间通信

8.  套接字接口

9.  使用 XTI 和 TLI 编程

10.  包过滤钩子

包过滤钩子接口

包过滤钩子内核函数

包过滤钩子数据类型

使用包过滤钩子接口

IP 实例

协议注册

事件注册

包钩子

包过滤钩子示例

11.  传输选择和名称到地址映射

12.  实时编程和管理

13.  Solaris ABI 和 ABI 工具

A.  UNIX 域套接字

索引

包过滤钩子示例

下面是一个完整示例,可以编译并装入到内核中。

可以使用以下命令将此代码编译到正在 64 位系统上运行的内核模块中。

# gcc -D_KERNEL -m64 -c full.c
# ld -dy -Nmisc/neti -Nmisc/hook -r full.o -o full

示例 10-1 包过滤钩子示例程序

/*
 * This file is a test module written to test the netinfo APIs in OpenSolaris.
 * 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) {

                sprintf(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);
}

示例 10-2 net_inject 示例程序

* 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,
};

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));
}