プログラミングインタフェース

第 10 章 パケットフィルタリングフック

パケットフィルタリングフックインタフェースにより、セキュリティ (パケットフィルタリングやファイアウォール) ソリューション、ネットワークアドレス変換 (Network Address Translation、NAT) ソリューションなど、カーネルレベルの付加価値ネットワークソリューションの開発が容易になります。

パケットフィルタリングフックインタフェースは、次の機能を提供します。

ループバックパケット傍受では、IP の共有インスタンスを使用しているゾーン間でパケットが移動するときに、それらのパケットにアクセスすることもできます。これはデフォルトのモデルです。

パケットフィルタリングフックインタフェース

パケットフィルタリングフックインタフェースには、カーネル関数とデータ型の定義が含まれます。

パケットフィルタリングフックのカーネル関数

パケットフィルタリングフックのカーネル関数は、パケットフィルタリングをサポートする misc/neti カーネルモジュールおよび misc/hook カーネルモジュールからエクスポートされます。これらの関数を使用するためには、カーネルモジュールを -Nmisc/neti-Nmisc/hook にリンクして、関数がカーネルによって正しく読み込まれるようにします。

hook_alloc(9F)

hook_t データ構造体を割り当てます。

hook_free(9F)

hook_alloc によって最初に割り当てられた hook_t() 構造体を解放します。

net_event_notify_register(9F)

指定されたイベントに対する変更が発生したときに呼び出される関数を登録します。

net_event_notify_unregister(9F)

指定されたコールバック関数の呼び出しによる、指定されたイベントに対する変更の通知をこれ以上受け取らないことを示します。

net_getifname(9F)

指定のネットワークインタフェースに指定された名前を取得します。

net_getlifaddr(9F)

指定された各論理インタフェースのネットワークアドレス情報を取得します。

net_getmtu(9F)

指定されたネットワークインタフェースの現在の MTU に関する情報を取得します。

net_getpmtuenabled(9F)

指定されたネットワークプロトコルに対してパス MTU (Path MTU、PMTU) 検出が有効かどうかを示します。

net_hook_register(9F)

指定されたネットワークプロトコルに属するイベントにコールバックを登録できるようにするフックを追加します。

net_hook_unregister(9F)

net_hook_register() によって登録されたコールバックフックを無効にします。

net_inject(9F)

ネットワーク層のパケットをカーネルまたはネットワークに配信します。

net_inject_alloc(9F)

net_inject_t 構造体を割り当てます。

net_inject_free(9F)

net_inject_alloc によって最初に割り当てられた net_inject_t() 構造体を解放します。

net_instance_alloc(9F)

net_instance_t 構造体を割り当てます。

net_instance_free(9F)

net_instance_alloc によって最初に割り当てられた net_instance_t() 構造体を解放します。

net_instance_notify_register(9F)

指定のネットワークインスタンスに対して新しいインスタンスが追加または削除されたときに呼び出される指定の関数を登録します。

net_instance_notify_unregister(9F)

指定されたコールバック関数の呼び出しによる、指定されたインスタンスに対する変更の通知をこれ以上受け取らないことを示します。

net_instance_register(9F)

IP インスタンスの保守に関連するイベントが発生するときに呼び出される関数のセットを記録します。

net_instance_unregister(9F)

net_instance_register() によって以前に登録されたインスタンスのセットを削除します。

net_ispartialchecksum(9F)

指定されたパケットに、部分チェックサム値のみを持つヘッダーが含まれるかどうかを示します。

net_isvalidchecksum(9F)

指定されたパケットのレイヤー 3 チェックサム、および場合によってはレイヤー 4 チェックサムを検査します。

net_kstat_create(9F)

IP の指定されたインスタンスの新しい kstat(9S) 構造体を割り当てて初期化します。

net_kstat_delete(9F)

IP の指定されたインスタンスの kstat をシステムから削除します。

net_lifgetnext(9F)

物理ネットワークインタフェースに関連付けられているすべての論理インタフェースを検索します。

net_phygetnext(9F)

ネットワークプロトコルが「所有」するすべてのネットワークインタフェースを検索します。

net_phylookup(9F)

ネットワークプロトコルの指定されたインタフェース名の取得を試行します。

net_protocol_lookup(9F)

ネットワーク層プロトコルの実装を検出します。

net_protocol_notify_register(9F)

指定のプロトコルに対する変更が発生するときに呼び出される指定の関数を登録します。

net_protocol_notify_unregister(9F)

呼び出す関数のリストから、指定された関数を削除します。

net_protocol_release(9F)

指定されたネットワークプロトコルへの参照が必要なくなったことを示します。

net_routeto(9F)

送信されるネットワークインタフェースパケットを示します。

パケットフィルタリングフックのデータ型

次の型が前述の関数をサポートしています。

hook_t(9S)

ネットワークイベントに挿入されるコールバック。

hook_nic_event(9S)

発生した、ネットワークインタフェースに属するイベント。

hook_pkt_event(9S)

フックに渡されるパケットイベント構造体。

net_inject_t(9S)

パケットの転送方法に関する情報。

net_instance_t(9S)

関連イベントが IP 内で発生するときに呼び出されるインスタンスのコレクション。

パケットフィルタリングフックインタフェースの使用

この API では IP スタックの複数のインスタンスを同じカーネルで同時に実行できるので、パケットフィルタリングフックインタフェースを操作するためには、ある程度の量のプログラミングが必要になります。IP スタックでは、ゾーンに対してその IP スタック自体の複数のインスタンスを使用でき、フレームワークの複数のインスタンスは、IP でのパケット傍受をサポートしています。

この節では、パケットフィルタリングフック API を使用してインバウンド IPv4 パケットを受信するコード例を示します。

IP インスタンス

この API を使用する場合は、まず、IP の複数のインスタンスを 1 つのカーネルで実行できるようにするか、大域ゾーンとやりとりするだけにするかを決定する必要があります。

IP インスタンスの存在がわかるように、インスタンスの作成、破棄、および終了時に起動されるコールバック関数を登録します。これら 3 つの関数ポインタを格納する net_instance_t() パケットイベント構造体を割り当てるには、net_instance_alloc を使用します。コールバックや構造体が必要なくなったときにリソースを解放するには、net_instance_free() を使用します。構造体のインスタンスに名前を指定するには、nin_name を指定します。少なくとも、nin_create() コールバックと nin_destroy() コールバックを指定します。IP の新しいインスタンスが作成されると nin_create() 関数が呼び出され、IP のインスタンスが破棄されると nin_destroy() 関数が呼び出されます。

nin_shutdown() の指定は、kstat に情報をエクスポートする場合を除いて任意です。インスタンス単位で kstat を使用するには、作成コールバック時に net_kstat_create() を使用します。kstat 情報のクリーンアップは、破棄コールバックではなく、終了コールバック時に行います。kstat 情報をクリーンアップするには、net_kstat_delete() を使用します。

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

net_instance_alloc() が呼び出される場合に IP のインスタンスが 1 つ以上あるときは、現在有効なインスタンスごとに作成コールバックが呼び出されます。コールバックをサポートするこのフレームワークでは、特定のインスタンスに対して一度に有効になるのは、作成関数、破棄関数、または終了関数のいずれか 1 つだけです。また、作成コールバックが呼び出されると、作成コールバックが完了するまで終了コールバックは呼び出されません。同様に、破棄コールバックは、終了コールバックが完了するまで開始されません。

次の例の mycreate() 関数は、作成コールバックの簡単な例です。mycreate() 関数は、ネットワークインスタンス識別子を独自の非公開のコンテキスト構造体に記録し、新しいプロトコル (IPv4 や IPv6 など) がこのフレームワークに登録されるときに呼び出される新しいコールバックを登録します。

ゾーンが実行されていないために大域ゾーン以外のインスタンスがない場合、net_instance_register() を呼び出すと、大域ゾーンに対して作成コールバックが実行されます。あとで net_instance_unregister() が呼び出されるように、破棄コールバックを指定してください。nin_create() フィールドまたは nin_destroy フィールドを NULL に設定して net_instance_register を呼び出すと、失敗します。

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

mynewproto() 関数は、ネットワークインスタンスに対してネットワークプロトコルが追加または削除されるたびに呼び出されることになります。登録したネットワークプロトコルが特定のインスタンス内ですでに動作中の場合、作成コールバックは、すでに存在するプロトコルごとに呼び出されます。

プロトコルの登録

このコールバックでは、proto 引数のみ呼び出し元によって指定されます。この時点では、有意なイベント名もフック名も指定できません。この例の関数では、IPv4 プロトコルの登録を通知するイベントのみ検索されます。

この関数では、次に、net_protocol_notify_register() インタフェースを使用して mynewevent() 関数を登録することによって IPv4 プロトコルにイベントが追加されたことを検出します。

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

次の表に、mynewproto() コールバックで使用されることがある 3 つのプロトコルを示します。今後、新しいプロトコルが追加される可能性があるので、不明なプロトコルは確実にエラー (戻り値 0) にしてください。

プログラミング記号 

プロトコル 

NHF_INET 

IPv4 

NHF_INET6 

IPv6 

NHF_ARP 

ARP 

イベントの登録

インスタンスやプロトコルの処理が動的であるのと同様に、各プロトコル下で行われるイベントの処理も動的です。この API では、ネットワークインタフェースイベントとパケットイベントの 2 種類のイベントがサポートされています。

次の関数では、IPv4 のインバウンドパケットのイベントが存在することの通知についてチェックされます。通知があると、hook_t 構造体が割り当てられ、インバウンド IPv4 パケットごとに呼び出される関数が記述されます。

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

mynewevent() 関数は、追加および削除されるイベントごとに呼び出されます。次のイベントを使用できます。

イベント名 

データ構造体 

コメント 

NH_PHYSICAL_IN 

hook_pkt_event_t 

このイベントは、ネットワークプロトコルに到達し、ネットワークインタフェースドライバから受信されたパケットごとに生成されます。 

NH_PHYSICAL_OUT 

hook_pkt_event_t 

このイベントは、ネットワークプロトコル層から送信するために、ネットワークインタフェースドライバに配信する前に、パケットごとに生成されます。 

NH_FORWARDING 

hook_pkt_event_t 

このイベントは、システムで受信されて別のネットワークインタフェースに送信されるすべてのパケットに対して生成されます。このイベントが発生するのは NH_PHYSICAL_IN の後と NH_PHYSICAL_OUT の前です。 

NH_LOOPBACK_IN 

hook_pkt_event_t 

このイベントは、ループバックインタフェースで受信されるパケット、またはネットワークインスタンスを大域ゾーンと共有しているゾーンで受信されるパケットに対して生成されます。 

NH_LOOPBACK_OUT 

hook_pkt_event_t 

このイベントは、ループバックインタフェースで送信されるパケット、またはネットワークインスタンスを大域ゾーンと共有しているゾーンで送信されているパケットに対して生成されます。 

NH_NIC_EVENTS 

hook_nic_event_t 

このイベントは、ネットワークインタフェースの状態の特定の変更に対して生成されます。 

パケットイベントの場合、IP スタックの特定のポイントごとに固有のイベントが 1 つあります。これは、パケットのフローにおいてパケットを傍受する正確な位置を選択できるようにするためであり、カーネル内で発生するすべてのパケットイベントを検査して過負荷状態になるのを避けることができます。ネットワークインタフェースイベントの場合、モデルは異なります。これは、1 つにはイベントの量が少ないためであり、また、必要とされるイベントが 1 つではなく複数であることが多いためです。

ネットワークインタフェースイベントは、次のイベントのいずれかを通知します。

今後、新しいネットワークインタフェースイベントが追加される可能性があるので、コールバック関数が受信した不明なイベントや認識できないイベントに対しては、常に 0 を返してください。

パケットフック

パケットフック関数は、パケットが受信されると呼び出されます。この場合、mypkthook() 関数は、物理ネットワークインタフェースからカーネルに受信するインバウンドパケットごとに呼び出されることになります。共有 IP インスタンスモデルを使用するゾーン間またはループバックインタフェース上を流れる、内部的に生成されたパケットは、対象になりません。

パケットを受け取ることと、パケットのドロップに必要なものを関数が正常に返すようにすることとの違いを示すために、次のコードでは、パケット 100 個ごとの発信元アドレスと宛先アドレスを出力し、パケットをドロップして、パケットロスを 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);
}

この関数で受信されたパケットと、パケットイベントからコールバックとして呼び出されるすべての要素は、1 つずつ受信されます。パケットとこのインタフェースは連鎖していないので、呼び出しごとにパケットは 1 個だけであり、b_next は常に NULL になります。ほかのパケットはありませんが、1 個のパケットが、b_cont と連鎖した複数の mblk_t 構造体で構成されることがあります。

パケットフィルタリングフックの例

コンパイルしてカーネルに読み込むことができる完全な例を次に示します。

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