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