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

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

この 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 構造体で構成されることがあります。