Programming Interfaces Guide

Chapter 10 Packet Filtering Hooks

The packet filtering hooks interfaces help develop value added network solutions at the kernel level such as security (packet filtering and firewall) solutions and network address translation (NAT) solutions.

The packet filtering hooks interfaces provide the following capabilities:

Loopback packet interception also provides access to packets as they move between zones that are using a shared instance of IP. This is the default model.

Packet Filtering Hooks Interfaces

Packet filtering hooks interfaces include kernel functions and data type definitions.

Packet Filtering Hooks Kernel Functions

The packet filtering hooks kernel functions are exported from the misc/neti and misc/hook kernel modules to support packet filtering. To use these functions, link your kernel modules with -Nmisc/neti and -Nmisc/hook so that the functions will be correctly loaded by the kernel.

hook_alloc(9F)

Allocate a hook_t data structure.

hook_free(9F)

Free a hook_t structure that was originally allocated by hook_alloc().

net_event_notify_register(9F)

Register a function to be called when there is a change to a specified event.

net_event_notify_unregister(9F)

Indicate that there is no longer any desire to receive notification of changes to the specified event through calls to the specified callback function.

net_getifname(9F)

Retrieve the name given to the specified network interface.

net_getlifaddr(9F)

Retrieve the network address information for each specified logical interface.

net_getmtu(9F)

Retrieve information about the current MTU of the specified network interface.

net_getpmtuenabled(9F)

Indicate whether path MTU (PMTU) discovery is enabled for the specified network protocol.

net_hook_register(9F)

Add a hook that allows callbacks to be registered with events that belong to the specified network protocol.

net_hook_unregister(9F)

Disable callback hooks that were registered with net_hook_register().

net_inject(9F)

Deliver network layer packets either into the kernel or onto the network.

net_inject_alloc(9F)

Allocate a net_inject_t structure.

net_inject_free(9F)

Free a net_inject_t structure that was originally allocated by net_inject_alloc().

net_instance_alloc(9F)

Allocate a net_instance_t structure.

net_instance_free(9F)

Free a net_instance_t structure that was originally allocated by net_instance_alloc().

net_instance_notify_register(9F)

Register the specified function to be called when there is a new instance added to or removed from the specified network instance.

net_instance_notify_unregister(9F)

Indicate that there is no longer any desire to receive notification of changes to the specified instance through calls to the specified callback function.

net_instance_register(9F)

Record the set of functions to be called when an event related to IP instance maintenance occurs.

net_instance_unregister(9F)

Remove the set of instances that were previously registered with net_instance_register().

net_ispartialchecksum(9F)

Indicates whether the specified packet contains headers with only partial checksum values.

net_isvalidchecksum(9F)

Verify the layer 3 checksum and, in some cases, the layer 4 checksum in the specified packet.

net_kstat_create(9F)

Allocate and initialize a new kstat(9S) structure for the specified instance of IP.

net_kstat_delete(9F)

Remove a kstat for the specified instance of IP from the system.

net_lifgetnext(9F)

Search all of the logical interfaces that are associated with a physical network interface.

net_phygetnext(9F)

Search all of the network interfaces that a network protocol “owns.”

net_phylookup(9F)

Attempt to retrieve the specified interface name for a network protocol.

net_protocol_lookup(9F)

Locate an implementation of a network layer protocol.

net_protocol_notify_register(9F)

Register the specified function to be called when there is a change to the specified protocol.

net_protocol_notify_unregister(9F)

Remove the specified function from the list of functions to call.

net_protocol_release(9F)

Indicate that a reference to the specified network protocol is no longer required.

net_routeto(9F)

Indicate which network interface packets are sent.

Packet Filtering Hooks Data Types

The following types support the functions described above.

hook_t(9S)

A callback to be inserted into a networking event.

hook_nic_event(9S)

An event that has occurred and belongs to a network interface.

hook_pkt_event(9S)

A packet event structure passed through to hooks.

net_inject_t(9S)

Information about how to transmit a packet.

net_instance_t(9S)

A collection of instances to be called when relevant events happen within IP.

Using the Packet Filtering Hooks Interfaces

A substantial amount of programming is required to work with the packet filtering hooks interfaces because this API supports multiple instances of the IP stack running concurrently in the same kernel. The IP stack allows multiple instances of itself for zones and multiple instances of the framework support packet interception in IP.

This section demonstrates the set up code to use the packet filtering hooks API to receive inbound IPv4 packets.

IP Instances

The first decision you need to make when you use this API is whether to accommodate multiple instances of IP running in the kernel or to only interact with the global zone.

To be aware of the presence of IP instances, register callback functions that are activated when an instance is created, destroyed, and shut down. Use net_instance_alloc() to allocate a net_instance_t packet event structure to store these three function pointers. Use net_instance_free() to free resources when you no longer need the callbacks and the structure. Specify nin_name to give the structure instance a name. Specify at least the nin_create() and nin_destroy() callbacks. The nin_create() function is called when a new instance of IP is created, and the nin_destroy() function is called when an instance of IP is destroyed.

Specifying nin_shutdown() is optional unless the code will be exporting information to kstats. To use kstats on a per-instance basis, use net_kstat_create() during the create callback. Cleanup of the kstat information must happen during the shutdown callback, not the destroy callback. Use net_kstat_delete() to clean up kstat information.

extern void *mycreate(const netid_t);

net_instance_t *n;

n = net_instance_alloc(NETINFO_VERSION);
if (n != NULL) {
    n->nin_create = mycreate;
    n->nin_destroy = mydestroy;
    n->nin_name = "my module";
    if (net_instance_register(n) != 0)
        net_instance_free(n);
}

If one or more instances of IP are present when net_instance_alloc() is called, the create callback will be called for each currently active instance. The framework that supports the callbacks ensures that only one of the create, destroy, or shutdown functions is active at any one time for a given instance. The framework also ensures that once the create callback has been called, the shutdown callback will only be called after create has completed. Similarly, the destroy callback does not start until the shutdown callback is complete.

The mycreate() function in the following example is a simple example of a create callback. The mycreate() function records the network instance identifier in its own private context structure and registers a new callback to be called when a new protocol (such as IPv4 or IPv6) is registered with this framework.

If no zones are running (and therefore no instances other than the global zone), calling net_instance_register() runs the create callback for the global zone. You must supply the destroy callback so that net_instance_unregister() can be called later. Attempts to call net_instance_register() with either the nin_create or nin_destroy fields set to NULL will fail.

void *
mycreate(const netid_t id)
{
    mytype_t *ctx;

    ctx = kmem_alloc(sizeof(*ctx), KM_SLEEP);
    ctx->instance_id = id;
    net_instance_notify_register(id, mynewproto, ctx);
    return (ctx);
}

The function mynewproto() should expect to be called each time a network protocol is either added to or removed from a networking instance. If registered network protocols are already operating within the given instance, then the create callback will be called for each protocol that already exists.

Protocol Registration

For this callback, only the proto argument is filled in by the caller. Neither an event nor a hook name can be meaningfully supplied at this point. In this example function, only events that announce the registration of the IPv4 protocol are being looked for.

The next step in this function is to discover when events are added to the IPv4 protocol by using the net_protocol_notify_register() interface to register the mynewevent() function.

static int
mynewproto(hook_notify_cmd_t cmd, void *arg, const char *proto,
    const char *event, const char *hook)
{
    mytype_t *ctx = arg;

    if (strcmp(proto, NHF_INET) != 0)
        return (0);

    switch (cmd) {
        case HN_REGISTER :
            ctx->inet = net_protocol_lookup(s->id, proto);
            net_protocol_notify_register(s->inet, mynewevent, ctx);
            break;
        case HN_UNREGISTER :
        case HN_NONE :
            break;
    }
    return (0);
}

The table below lists all three protocols that could be expected to be seen with the mynewproto() callback. New protocols could be added in the future, so you must safely fail (return the value 0) any unknown protocols.

Programming Symbol 

Protocol 

NHF_INET 

IPv4 

NHF_INET6 

IPv6 

NHF_ARP 

ARP 

Event Registration

Just as the handling of instances and protocols is dynamic, the handling of the events that live under each protocol also is dynamic. Two types of events are supported by this API: network interface events and packet events.

In the function below, the announcement for the presence of the event for inbound packets for IPv4 is being checked for. When that announcement is seen, a hook_t structure is allocated, describing the function to be called for each inbound IPv4 packet.

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

    if ((strcmp(event, NH_PHYSICAL_IN) == 0) &&
        (strcmp(parent, NHF_INET) == 0)) {
            sprintf(buffer, "mypkthook_%s_%s", parent, event);
            h = hook_alloc(HOOK_VERSION);
            h->h_hint = HH_NONE;
            h->h_arg = s;
            h->h_name = strdup(buffer);
            h->h_func = mypkthook;
            s->hook_in = h;
            net_hook_register(ctx->inet, (char *)event, h);
    } else {
            h = NULL;
    }
    return (0);
}

The function mynewevent() will be called for each event that is added and removed. The following events are available.

Event Name 

Data Structure 

Comment 

NH_PHYSICAL_IN 

hook_pkt_event_t 

This event is generated for every packet that arrives at the network protocol and has been received from a network interface driver. 

NH_PHYSICAL_OUT 

hook_pkt_event_t 

This event is generated for every packet prior to delivery to the network interface driver for sending from the network protocol layer. 

NH_FORWARDING 

hook_pkt_event_t 

This event is for all packets that have been received by the system and will be sent out another network interface. This event happens after NH_PHYSICAL_IN and before NH_PHYSICAL_OUT. 

NH_LOOPBACK_IN 

hook_pkt_event_t 

This event is generated for packets that are received on the loopback interface or that are received by a zone that is sharing its network instance with the global zone. 

NH_LOOPBACK_OUT 

hook_pkt_event_t 

This event is generated for packets that are sent on the loopback interface or that are being sent by a zone that is sharing its network instance with the global zone. 

NH_NIC_EVENTS 

hook_nic_event_t 

This event is generated for specific changes of state for network interfaces. 

For packet events, there is one specific event for each particular point in the IP stack. This is to enable you to be selective about exactly where in the flow of the packets you wish to intercept packets, without being overburdened by examining every packet event that happens inside the kernel. For network interface events the model is different, in part because the events are much lower in volume and because it is more likely that the developer will be interested in several of them, not just one.

The network interface event announces one of the following events:

New network interface events could be added in the future, so you must always return 0 for any unknown or unrecognized event that the callback function receives.

The Packet Hook

The packet hook function is called when a packet is received. In this case the function mypkthook() should expect to be called for each inbound packet that arrives in the kernel from a physical network interface. Packets generated internally, that flow between zones using the shared IP instance model or over the loopback interface, will not be seen.

To illustrate the difference between accepting a packet and allowing the function to return normally with what is required to drop a packet, the code below prints out the source and destination address of every 100th packet and then drops the packet, introducing a packet loss of 1%.

static int
mypkthook(hook_event_token_t tok, hook_data_t data, void *arg)
{
    static int counter = 0;
    mytupe_t *ctx = arg;
    hook_pkt_event_t *pkt = (hook_pkt_event_t)data;
    struct ip *ip;
    size_t bytes;

    bytes = msgdsize(pkt->hpe_mb);

    ip = (struct ip *)pkt->hpe_hdr;

    counter++;
    if (counter == 100) {
        printf("drop %d bytes received from %x to %x\n", bytes,
            ntohl(ip->ip_src.s_addr), ntohl(ip->ip_dst.s_addr));
        counter = 0;
        freemsg(*pkt->hpe_mp);
        *pkt->hpe_mp = NULL;
        pkt->hpe_mb = NULL;
        pkt->hpe_hdr = NULL;
        return (1);
    }
    return (0);
}

Packets received by this function, and all others that are called as a callback from a packet event, are received one at a time. There is no chaining together of packets with this interface, so you should expect only one packet per call and expect b_next to always be NULL. While there is no other packet, a single packet may be comprised of several mblk_t structures chained together with b_cont.

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 10–1 Packet Filtering Hooks Example Program

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