特別講座 ネットワークプログラミング ( FWをつくろう )


ファイアウォール(FW)とは,「ある特定のコンピュータネットワークとその外部との通信を制御し、内部のコンピュータネットワークの安全を維持することを目的としたソフトウェア」である.
つまり,特定のパケットを破棄(または通過)させる制御を行うソフトウェアである.

特定のパケットとは,

などがある.

今回は,以下の図のような構成のネットワークにFWを設置する環境を想定する.
今回のFWはルータの機能はなく,あくまでPCとルータ間のパケットを監視し,不要なデータを遮断する機能を持つ.


FWソフトウェアを作成するには,

2つのプログラミングが必要になる.

ここでは,上記の2つのプログラミング方法の解説を行う.

前提知識

今回使用する「C言語中級に出てこなかったC言語の基本的な仕様」を解説する.

パケットキャプチャ

パケットの中身を見るソフトウェアをパケットキャプチャと呼ぶ.
まずはパケットキャプチャを作成する.
一般的なパケットキャプチャは,TCPやUDPはもとより,IPやEthernetレベルのデータリンクの情報も解析する.
ここで作成するプログラムは,データリンクはEthernetをほぼ限定し,Ethernetヘッダ,IPヘッダ,TCPヘッダなどを解析するプログラムを作成する.

一般的なパケットキャプチャソフトウェアとして,tcpdumpやwiresharkなどがあるが,ここではそれと同等の解析を行うソフトウェアを自作する.

Raw Socket

IP層以下のヘッダ情報は,基本的にC言語以外では解析するためのAPIが存在しない.
C言語ではRaw Socketと呼ばれるSocketを使うことで,パケットの生(Raw)データを扱える.
packetcapture.c : main()
#include <stdio.h>
#include <net/if.h> //PF_PACKET
#include <net/ethernet.h> //ETH_P_ALL

int main(){
   int soc;
   u_char buf[65535];
   soc = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
   while(1){
      read(soc,buf,sizeof(buf));
      analyzePacket(buf);
   }
}
socket()は,ソケットを作成するシステムコールである.
int socket(int domain, int type, int protocol);

domainには,PF_PACKET(またはAF_PACKET)を指定すると,データリンク層やネットワーク層の生データを扱える.
この際にtypeにはSOCK_RAWを指定するとデータリンク層から扱え,SOCK_DGRAMを指定するとネットワーク層から扱える.

※ 通常のsocketは,domainにPF_INET(AF_INET),typeにSOCK_STREAM(TCPの場合)やSOCK_DGRAM(UDPの場合)を指定する.

protocol には IEEE 802.3 プロトコル番号を ネットワークバイトオーダーで指定する.
指定できるプロトコルのリストは,/usr/include/linux/if_ether.h に記載されている.
プロトコルを htons(ETH_P_ALL) にすると,データリンク層から全てのデータが受信できる.

またhtons(ETH_P_IP)にすると,ネットワーク層からのデータを扱える.

※htons()については後述する.

socket()システムコールは,生成に成功すると正の値を返す.
この値はディスクリプタとして,以後入出力に使える.

ここで少しC言語のファイルディスクリプタについて解説を入れておく.
Cでは,open・read・write・close などの,低レベルの処理を行う際に,読み出し元,書き出し先などを整数値で管理する.
プロセスが起動すると,標準入力が0,標準出力が1,標準エラー出力が2に割り当てられる.
以後,ファイルをオープンしたりsocketシステムコールなどを使うと,3,4,...と割り当てられる.

analyzePacket(),printEtherHeader()
void analyzePacket(u_char *buf){
    printEtherHeader(buf);
}

void printEtherHeader(u_char *buf){
    struct ether_header *eth;
    eth = (struct ether_header *)buf;
    printf("----------- ETHERNET -----------\n");
    printf("Dst MAC addr   : %17s \n",mac_ntoa(eth->ether_dhost));
    printf("Src MAC addr   : %17s \n",mac_ntoa(eth->ether_shost));
    printf("Ethernet Type  : 0x%04x\n",ntohs(eth->ether_type));
}

char *mac_ntoa(u_char *d){
    static char str[18];
    sprintf(str,"%02x:%02x:%02x:%02x:%02x:%02x",d[0],d[1],d[2],d[3],d[4],d[5]);
    return str;
}
Cでネットワークの生データを扱う場合,生データをヘッダの構造体にキャストして構造体のメンバにアクセスすることでデータを解析する.

printEtherHeader()内の

eth = (struct ether_header *)buf;
に注目する.
bufは符号なしcharのポインタであるが,ethはether_header構造体のポインタである.
ネットワークの生データは,プロトコル仕様に則ってデータが配置しているため, u_charのバイト列で扱っている生データをプロトコルのヘッダ仕様を記述した構造体のポインタにキャストすることで,各フィールドの値を個別に取り扱える.

OSによるデータリンク層プログラミングの違い

Linuxでは,SocketシステムコールにSOCK_RAWを指定することで,データリンクのパケットを扱えた.
余談ではあるが,ここでは他のOSでのデータリンク層プログラミングについて少し解説する.

エンディアン

htonsやntohsを理解するには,エンディアンについての理解が必要になる.
エンディアンとはバイトオーダとも呼ばれ,データをメモリに配置する方式を指す.

エンディアンには,ビッグエンディアンとリトルエンディアンの2種類が主に存在する.

16進数1234ABをメモリ上に配置する際,「12 34 AB」と配置する方式をビッグエンディアン, 「AB 34 12」と並べる方式をリトルエンディアンと呼ぶ.
SolarisのSPARCなどはビッグエンディアン方式で,Intelのx86系はリトルエンディアン方式を採用している.
ビッグエンディアンは,人にとって直感的にわかりやすく,リトルエンディアンは,計算機が処理しやすいという特徴があり,CPUによって採用方式は異なる.

参考までに,Javaの仮想マシンはOSを問わずビッグエンディアンであり,また,インターネット上を流れるデータのフォーマットもビッグエンディアンである.

ntohs()は,Network to Host short の略であり,ネットワークバイトオーダ(ビッグエンディアン)をホストで採用しているエンディアンに変換する関数で,2バイト(short)を対象にする.
htons()は,逆にホストのエンディアンをネットワークのエンディアンに変換する.
この時,ホストのCPUがビッグエンディアンを採用している場合は,htons()やntohs()を行っても何も変更されない.
またshort以外にもlongを扱うhtonl()やntohl()なども存在する.

当然であるが,char型(1バイト)のデータは,エンディアン変換を行う必要がない. printEtherHeader()では,ether_header構造体にキャストした後,各要素を表示している.
mac_ntoaは,MACアドレスを16進の一般的なMACアドレス表示形式に変更するオリジナルの関数である.

IPパケット

これでデータリンク(ETHERNET)のヘッダを見ることができた.
次に,IPヘッダを見るために,analyzePacket()を以下のように変更する.
analyzePacket()改
void analyzePacket(u_char *buf){
    u_char *ptr;
    struct ether_header *eth;
    printEtherHeader(buf);
    ptr = buf;
    eth = (struct ether_header *)buf;

    switch(ntohs(eth->ether_type)){
    case ETH_P_IP:
        printf("IP Packet\n");
        break;
    case ETH_P_IPV6:
        printf("IPv6 Packet\n");
        break;
    case ETH_P_ARP:
        printf("ARP Packet\n");
        break;
    default:
        printf("unknown\n");
    }
}
この変更によってETHERNETヘッダのタイプの値により,次のヘッダの情報がわかる.
0x0800であればIP,0x86ddであればIPv6,0x0806であればARPのパケットである.
こちらの定数は,/usr/include/linux/if_ether.hに記述してある.
IPヘッダを見るには,IPのパケットでなければならないので,ここで場合わけをし, IPの場合だけ,ヘッダを表示させるように変更する.
analyzePacket()改2とprintIPHeader()
#include <netinet/ip.h> //struct iphdr用 (汎用的なstruct ipもある)

void analyzePacket(u_char *buf){
    u_char *ptr;
    struct ether_header *eth;
    printEtherHeader(buf);
    ptr = buf;
    eth = (struct ether_header *)buf;
    ptr += sizeof(struct ether_header);
    switch(ntohs(eth->ether_type)){
    case ETH_P_IP:
        printIPHeader(ptr);
        break;
    case ETH_P_IPV6:
        printf("IPv6 Packet\n");
        break;
    case ETH_P_ARP:
        printf("ARP Packet\n");
        break;
    default:
        printf("unknown\n");
    }
}

char *ip_ntoa(u_int32_t ip){
	u_char *d = (u_char *)&ip;
	static char str[15];
	sprintf(str,"%d.%d.%d.%d",d[0],d[1],d[2],d[3]);
	return str;
}

void printIPHeader(u_char *buf){
    struct iphdr *ip;
    ip = (struct iphdr *)buf;
    printf("----------- IP -----------\n");
    printf("version=%u\n",ip->version);
    printf("ihl=%u\n",ip->ihl);
    printf("tos=%x\n",ip->tos);
    printf("tot_len=%u\n",ntohs(ip->tot_len));
    printf("id=%u\n",ntohs(ip->id));
    printf("ttl=%u\n",ip->ttl);
    printf("protocol=%u\n",ip->protocol);
    printf("src addr=%s\n",ip_ntoa(ip->saddr));
    printf("dst addr=%s\n",ip_ntoa(ip->daddr));
}


ここではanalyzePacket()からprintIPHeader()を呼んでいるが,printEtherHeader()から 呼ぶようにしても構わない.
IPヘッダの詳細はここでは省くが,各ヘッダの値を表示できることが確認できるだろう.
IPヘッダのprotocolフィールドがIPヘッダの次のヘッダの情報になる.
この値が1であればICMP,6であればTCP,17であればUDPというように分類できる.

ARP

ARPでもIPヘッダ表示と同じように内容を表示できる.
printArp()
#include <netinet/if_ether.h> //struct ether_arp

void analyzePacket(u_char *buf){

   ~中略~

    case ETH_P_ARP:
        printArp(ptr);
        break;

   ~中略~

}

char *ip_ntoa2(u_char *d){
    static char str[15];
    sprintf(str,"%d.%d.%d.%d",d[0],d[1],d[2],d[3]);
    return str;
}

void printArp(u_char *buf){
    struct ether_arp *arp;
    arp =(struct ether_arp *)buf;
    printf("----------- ARP ----------\n");
    printf("arp_hrd=%u\n",ntohs(arp->arp_hrd));
    printf("arp_pro=%u\n",ntohs(arp->arp_pro));
    printf("arp_hln=%u\n",arp->arp_hln);
    printf("arp_pln=%u\n",arp->arp_pln);
    printf("arp_op=%u\n",ntohs(arp->arp_op));
    printf("arp_sha=%s\n",mac_ntoa(arp->arp_sha));
    printf("arp_spa=%s\n",ip_ntoa2(arp->arp_spa));
    printf("arp_tha=%s\n",mac_ntoa(arp->arp_tha));
    printf("arp_tpa=%s\n",ip_ntoa2(arp->arp_tpa));
}

新たにip_ntoa2()を作成せずとも
	printf("arp_tpa=%s\n",ip_ntoa(*((u_int32_t *)arp->arp_tpa)));
でもよい.

TCPヘッダ

ここではさらにTCPの場合に使用するポート番号を表示してみよう.
analyzePacket()改3とprintTcpHeader()
#include <netinet/tcp.h> //struct tcp

void analyzePacket(u_char *buf){
    u_char *ptr;
    struct ether_header *eth;
    struct iphdr *ip;
    printEtherHeader(buf);
    ptr = buf;
    eth = (struct ether_header *)buf;
    ptr += sizeof(struct ether_header);
    switch(ntohs(eth->ether_type)){
    case ETH_P_IP:
        printIPHeader(ptr);
        ip = (struct iphdr *)ptr;
        if(ip->protocol==6){
             ptr+=((struct iphdr *)ptr)->ihl*4;
             printTcpHeader(ptr);
        }
        break;
    case ETH_P_IPV6:
        printf("IPv6 Packet\n");
        break;
    case ETH_P_ARP:
        printArp(ptr);
        break;
    default:
        printf("unknown\n");
    }
}

void printTcpHeader(u_char *buf){
    struct tcphdr *ptr;
    ptr = (struct tcphdr *)buf;
    printf("----------- TCP ----------\n");
    printf("src port = %u\n",ntohs(ptr->source));
    printf("dst port = %u\n",ntohs(ptr->dest));
}

IPヘッダの長さは,IPヘッダのデータ長フィールドに格納されている.
具体的には,iphdr構造体のihlメンバに格納されている.
IPヘッダの仕様を見ればわかるが,ここには,IPヘッダの長さ÷4の値が格納されているので, 実際の長さは,このメンバを4倍した値がヘッダ長になる.
IPヘッダはオプションフィールドに何もデータがない場合20オクテット(バイト)になるため, たいていの場合,ihlには5が入っていることになる.

このプログラムを実行すると,TCPだった場合のみ,送信元ポート番号と送信先ポート番号が表示されることが確認できる.

FWは,送信元,送信先IPアドレスやポート番号で中継許可・不可の判断をすることが多いが, この程度のプログラムが作れれば,通過させてよいパケットであるかどうかの判断をすることができることになる.

ここまでのソース
packetcapture.c


データ転送

FWは2つ以上のネットワークデバイスを持ち,片方から受信したパケットをある判断に基づいて,他のインタフェースに送信または,そのままドロップさせる処理を行う.
このため,最低限リピータやブリッジと呼ばれるような中継機器の動作を行える必要がある.
先ほどパケットキャプチャプログラムは,全インタフェースを対象にパケットキャプチャを行ったが,リピータやブリッジの機能を搭載させるには,ネットワークデバイスを特定して受信を行い,他のネットワークデバイスに送信する必要がある.

確認

/proc/sys/net/ipv4/ip_forward の値が0になっていることを確認すること.
このファイルの値が1になっている場合は,パケット転送機能がONになっているので,リピータやブリッジのプログラムを作成した際に思わぬ動作を引き起こすため,0にしておくこと.

ioctl()とbind()システムコール

まずは,特定のインタフェースの指定する方法を試してみよう. ソケットの初期化を行い部分をmain関数から1つの独立した関数initRawSocket()として 引数にインタフェースの名前を指定して初期化できるように変更する.
initRawSocket()
#include <sys/ioctl.h> /* SIOCGIFFLAG SIOCSIFFLAG SIOCGIFINDEX */ 

int initRawSocket(char *dev){
    struct ifreq ifr;
    int soc;
    struct sockaddr_ll sa;
    soc = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));

    //初期化
    memset(&ifr,0,sizeof(struct ifreq));
    strncpy(ifr.ifr_name,dev,sizeof(ifr.ifr_name)-1);

    ioctl(soc,SIOCGIFINDEX,&ifr); //ifrにeth0の情報格納

    sa.sll_family=PF_PACKET;
    sa.sll_protocol=htons(ETH_P_ALL);
    sa.sll_ifindex=ifr.ifr_ifindex;
    bind(soc,(struct sockaddr *)&sa,sizeof(sa));//ifをbind, bindしないとすべてのifが対象
	
    return soc;
}

ifreqとsockaddr_llの2つの構造体と,ioctl()とbind()の2つのシステムコールが初めて登場する.
またキャスト時にsockaddr構造体が登場する.

ioctl()はネットワークプログラミング時のみに登場するシステムコールではなく, デバイスドライバに制御用のメッセージを送受信するためのシステムコールである.
第1引数は,そのデバイスへのファイルディスクリプタ(今回はsocketのファイルディスクリプタでよい)を指定する.
第2引数には,そのデバイスへの制御メッセージ(デバイスにより異なる)を入れる.
第3引数には,制御用のメッセージを格納する構造体を入れる.
(定義では voidへのポインタになっているので,ポインタであれば文法的には何でもよい)

ioctl()の対象がネットワークデバイスの場合は,ifreq構造体を制御用に使うことになっている.

ifreq構造体には,ifr_nameという文字列のメンバがあり,ここにインタフェースの名前を格納する.
それ以外のメンバは共用体になっており,メモリを共有し,仕様用途によって異なるメンバを使用する.
SIOCGIFINDEXを第2引数に指定すると,ioctl()で得た情報をifreqのifr_ifindexに格納する.
この情報をbind()で使うためにsockaddr_ll構造体を作成するために使用する.
sockaddr_ll構造体は,リンクレイヤを扱うsocketの情報を格納する構造体で, bind()で使用する際には,sockaddr構造体にキャストして使用する.
sockaddr構造体は,Javaでいう抽象クラスのような構造体で,実態としてはsockaddr_llやsockaddr_inなどの構造体の情報が使われる.

bind()システムコールは,socと使用するインタフェースをマッピングするシステムコールで, 以後socで読み書きするデータは,ifreqで指定したネットワークデバイスのみに限定する.

この変更を先ほどのパケットキャプチャプログラムに適用すると,特定のネットワークデバイスで受信するパケットのみを解析するプログラムになる.

promiscuous mode

パケットキャプチャプログラムは,自身のネットワークデバイス(自身のMACアドレス)宛に送られてきたパケットのみを受信し,socketで読み出すことができた.
FWは通常通信経路の途中に設置し,他と他の通信を傍受し,あるポリシーに従ってパケットを破棄する仕事を行う.
このため,自分のネットワークデバイス宛以外のパケットも受信し,ヘッダを解析する必要がある.
また,ブリッジやリピータも,中継機器として,自身のネットワークデバイスのアドレスとは無関係の宛先が格納されているパケットを転送する必要がある.
しかしながら通常のネットワークデバイスは,自分のMACアドレス以外の宛先のパケットは,受信してもsocketで読み出し可能なデータにする前に破棄してしまう.
そのため,FWやリピータ,ブリッジのような機能を実装するには,ネットワークデバイスでどのような宛先のデータを受信しても破棄しないように設定する必要がある.
ネットワークデバイスを上記のような上記のような状態にすることをpromiscuous modeをONにするという.
プログラムではInitRawSocket()内の最後に,以下のように記述する.
promiscuous mode
int initRawSocket(char *dev){
    struct ifreq ifr;
    int soc,size;
    struct sockaddr_ll sa;
    soc = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));

    ~中略~

    ioctl(soc,SIOCGIFFLAGS,&ifr); //ifrにeth0の情報格納
    ifr.ifr_flags |= IFF_PROMISC ; //promisc オプションを付加
    ioctl(soc,SIOCSIFFLAGS,&ifr); //ifrの情報を設定
return soc;
}

ioctl()でSIOCGIFFLAGSを指定し,ifrにネットワークデバイスの情報を取得する.
その後,ifrのメンバのifr_flagにIFF_PROMISCフラグをセットする.(promiscuous modeをONにする)
最後に,ioctl()を使ってSIOCSIFFLAGSで情報をデバイスにセットする.
以後,このsocでは,自身のネットワークデバイスに設定されているMACアドレス以外のアドレス宛のデータもread()で読み出すことができる.

転送

パケットに変更を加えずそのまま別のネットワークデバイスに転送するには,read()で読み込んだデータを変更せずに別ネットワークデバイスのディスクリプタにwrite()すればよい.
パケットを転送するmain()
int main(){
    int i,size,flag;
    u_char buf[65535];
    int soc[2];
    char *dev[2] ={"wlan0","eth0"};

    for(i=0;i<2;i++)
       soc[i]=initRawSocket(dev[i]);
	
    while(1){
        size = read(soc[0],buf,sizeof(buf));
        flag = analyzePacket(buf);
        if(flag)write(soc[1],buf,size);
    }
}

ここではanalyzePacket()関数の返り値をintにし,解析した結果転送してよい場合は1,破棄する場合は0を返す関数に変更したと仮定し,それをflag変数に格納してwrite()を呼び出している.
上記の例の場合はwlan0から受信したデータを解析し,analyzePacket()関数に転送許可の判断をさせ,転送させてもよい場合は,eth0へ転送している.
転送先(この場合eth0)のインタフェースでパケットキャプチャプログラムを動かし,wlan0へ何かのデータを送ってみよう.
この時に,パケットキャプチャプログラムが,そのパケットを表示していれば転送はうまくいっている.
ネットワークデバイスが1つしかない場合は,ループバックインタフェース"lo"を使ってもよい.

複数のネットワークデバイスを同時に扱うpoll()システムコール

先ほどのプログラムは,片方のネットワークデバイスに到達したパケットをもう片方のネットワークデバイスに転送するプログラムであった.
poll()システムコールを使うことで,ネットワークデバイスに受信可能なデータが到達した場合のみ 動作を行うプログラムを記述でき,双方向通信を実現できる.
poll()システムコールは引数にpollfd構造体を使用する.
pollfd構造体は,監視すべきイベントを格納したり,イベント検知後の通知が格納される.
使用例を以下に示す.

poll()を使用し,双方向転送するmain()
int main(){
    int i,size,flag;
    u_char buf[65535];
    char *dev[2] ={"wlan0","eth0"};
    struct pollfd iflist[2];

    for(i=0;i<2;i++){
        iflist[i].fd=initRawSocket(dev[i]);
        iflist[i].events = POLLIN;
    }

    while(1){
      switch(poll(iflist,2,100)){
        case -1:
            perror("poll");
            break;
        case 0:
            break;
        default:
            for(i=0;i<2;i++){
                if(iflist[i].revents&(POLLIN)){
                    size = read(iflist[i].fd,buf,sizeof(buf));
                    printf("recv from %s (%d octets)\n",dev[i],size);
                    flag = analyzePacket(buf);
                    if(flag){
                        write(iflist[!i].fd,buf,size);
                        printf("send to %s (%d octets)\n",dev[!i],size);
                    }
                }
            }
        }
    }
}

pollfd構造体のeventsにPOLLINを格納しておくと,fdに格納されたディスクリプタで 受信可能がデータが発生した場合,reventsにPOLLINが格納される.
これを利用してreventsにPOLLINが格納されたネットワークデバイスからread()を行い, もう片方のネットワークデバイスへ転送している.

poll()を使わず,2つのプロセスを動かしてもよいし,Threadを用いても良いが, 1つのプロセスで行えるpoll()を使った方法も身につけておくと良い.

ここまでのソース
fw.c


今回使用する構造体一覧

struct ether_header

場所:/user/include/net/ethernet.h
struct ether_header
struct ether_header
{
  u_int8_t  ether_dhost[ETH_ALEN];	/* destination eth addr	*/
  u_int8_t  ether_shost[ETH_ALEN];	/* source ether addr	*/
  u_int16_t ether_type;		        /* packet type ID field	*/
} __attribute__ ((__packed__));

/* Ethernet protocol ID's */
#define	ETHERTYPE_PUP		0x0200          /* Xerox PUP */
#define ETHERTYPE_SPRITE	0x0500		/* Sprite */
#define	ETHERTYPE_IP		0x0800		/* IP */
#define	ETHERTYPE_ARP		0x0806		/* Address resolution */
#define	ETHERTYPE_REVARP	0x8035		/* Reverse ARP */
#define ETHERTYPE_AT		0x809B		/* AppleTalk protocol */
#define ETHERTYPE_AARP		0x80F3		/* AppleTalk ARP */
#define	ETHERTYPE_VLAN		0x8100		/* IEEE 802.1Q VLAN tagging */
#define ETHERTYPE_IPX		0x8137		/* IPX */
#define	ETHERTYPE_IPV6		0x86dd		/* IP protocol version 6 */
#define ETHERTYPE_LOOPBACK	0x9000		/* used to test interfaces */


以下は/usr/include/linux/if_ether.hに定義

#define ETH_ALEN	6		/* Octets in one ethernet addr	 */

struct iphdr

場所:/usr/include/netinet/ip.h
struct iphdr
struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error	"Please fix "
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
  };

以下/usr/include/netinet/in.hの情報

/* Standard well-defined IP protocols.  */
enum
  {
    IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
#define IPPROTO_IP		IPPROTO_IP
    IPPROTO_HOPOPTS = 0,   /* IPv6 Hop-by-Hop options.  */
#define IPPROTO_HOPOPTS		IPPROTO_HOPOPTS
    IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
#define IPPROTO_ICMP		IPPROTO_ICMP
    IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
#define IPPROTO_IGMP		IPPROTO_IGMP
    IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
#define IPPROTO_IPIP		IPPROTO_IPIP
    IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
#define IPPROTO_TCP		IPPROTO_TCP
    IPPROTO_EGP = 8,	   /* Exterior Gateway Protocol.  */
#define IPPROTO_EGP		IPPROTO_EGP
    IPPROTO_PUP = 12,	   /* PUP protocol.  */
#define IPPROTO_PUP		IPPROTO_PUP
    IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */
#define IPPROTO_UDP		IPPROTO_UDP
    IPPROTO_IDP = 22,	   /* XNS IDP protocol.  */
#define IPPROTO_IDP		IPPROTO_IDP
    IPPROTO_TP = 29,	   /* SO Transport Protocol Class 4.  */
#define IPPROTO_TP		IPPROTO_TP
    IPPROTO_DCCP = 33,	   /* Datagram Congestion Control Protocol.  */
#define IPPROTO_DCCP		IPPROTO_DCCP
    IPPROTO_IPV6 = 41,     /* IPv6 header.  */
#define IPPROTO_IPV6		IPPROTO_IPV6
    IPPROTO_ROUTING = 43,  /* IPv6 routing header.  */
#define IPPROTO_ROUTING		IPPROTO_ROUTING
    IPPROTO_FRAGMENT = 44, /* IPv6 fragmentation header.  */
#define IPPROTO_FRAGMENT	IPPROTO_FRAGMENT
    IPPROTO_RSVP = 46,	   /* Reservation Protocol.  */
#define IPPROTO_RSVP		IPPROTO_RSVP
    IPPROTO_GRE = 47,	   /* General Routing Encapsulation.  */
#define IPPROTO_GRE		IPPROTO_GRE
    IPPROTO_ESP = 50,      /* encapsulating security payload.  */
#define IPPROTO_ESP		IPPROTO_ESP
    IPPROTO_AH = 51,       /* authentication header.  */
#define IPPROTO_AH		IPPROTO_AH
    IPPROTO_ICMPV6 = 58,   /* ICMPv6.  */
#define IPPROTO_ICMPV6		IPPROTO_ICMPV6
    IPPROTO_NONE = 59,     /* IPv6 no next header.  */
#define IPPROTO_NONE		IPPROTO_NONE
    IPPROTO_DSTOPTS = 60,  /* IPv6 destination options.  */
#define IPPROTO_DSTOPTS		IPPROTO_DSTOPTS
    IPPROTO_MTP = 92,	   /* Multicast Transport Protocol.  */
#define IPPROTO_MTP		IPPROTO_MTP
    IPPROTO_ENCAP = 98,	   /* Encapsulation Header.  */
#define IPPROTO_ENCAP		IPPROTO_ENCAP
    IPPROTO_PIM = 103,	   /* Protocol Independent Multicast.  */
#define IPPROTO_PIM		IPPROTO_PIM
    IPPROTO_COMP = 108,	   /* Compression Header Protocol.  */
#define IPPROTO_COMP		IPPROTO_COMP
    IPPROTO_SCTP = 132,	   /* Stream Control Transmission Protocol.  */
#define IPPROTO_SCTP		IPPROTO_SCTP
    IPPROTO_UDPLITE = 136, /* UDP-Lite protocol.  */
#define IPPROTO_UDPLITE		IPPROTO_UDPLITE
    IPPROTO_RAW = 255,	   /* Raw IP packets.  */
#define IPPROTO_RAW		IPPROTO_RAW
    IPPROTO_MAX
  };

struct ether_arp

場所:/usr/include/netinet/if_ether.h
struct ether_arp
struct	ether_arp {
	struct	arphdr ea_hdr;		/* fixed-size header */
	u_int8_t arp_sha[ETH_ALEN];	/* sender hardware address */
	u_int8_t arp_spa[4];		/* sender protocol address */
	u_int8_t arp_tha[ETH_ALEN];	/* target hardware address */
	u_int8_t arp_tpa[4];		/* target protocol address */
};
#define	arp_hrd	ea_hdr.ar_hrd
#define	arp_pro	ea_hdr.ar_pro
#define	arp_hln	ea_hdr.ar_hln
#define	arp_pln	ea_hdr.ar_pln
#define	arp_op	ea_hdr.ar_op

以下は/usr/include/net/if_arp.hに定義
struct arphdr
  {
    unsigned short int ar_hrd;		/* Format of hardware address.  */
    unsigned short int ar_pro;		/* Format of protocol address.  */
    unsigned char ar_hln;		/* Length of hardware address.  */
    unsigned char ar_pln;		/* Length of protocol address.  */
    unsigned short int ar_op;		/* ARP opcode (command).  */
#if 0
    /* Ethernet looks like this : This bit is variable sized
       however...  */
    unsigned char __ar_sha[ETH_ALEN];	/* Sender hardware address.  */
    unsigned char __ar_sip[4];		/* Sender IP address.  */
    unsigned char __ar_tha[ETH_ALEN];	/* Target hardware address.  */
    unsigned char __ar_tip[4];		/* Target IP address.  */
#endif
  };

struct tcphdr

場所:/usr/include/netinet/tcp.h
struct tcphdr
struct tcphdr
  {
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
#  if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
#  elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
#  else
#   error "Adjust your  defines"
#  endif
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_ptr;
};

struct ifreq

場所:/usr/include/net/if.h
struct ifreq
struct ifreq
  {
# define IFHWADDRLEN	6
# define IFNAMSIZ	IF_NAMESIZE
    union
      {
	char ifrn_name[IFNAMSIZ];	/* Interface name, e.g. "en0".  */
      } ifr_ifrn;

    union
      {
	struct sockaddr ifru_addr;
	struct sockaddr ifru_dstaddr;
	struct sockaddr ifru_broadaddr;
	struct sockaddr ifru_netmask;
	struct sockaddr ifru_hwaddr;
	short int ifru_flags;
	int ifru_ivalue;
	int ifru_mtu;
	struct ifmap ifru_map;
	char ifru_slave[IFNAMSIZ];	/* Just fits the size */
	char ifru_newname[IFNAMSIZ];
	__caddr_t ifru_data;
      } ifr_ifru;
  };

struct sockaddr_ll

場所:/usr/include/netpacket/packet.h
struct sockaddr_ll
struct sockaddr_ll
  {
    unsigned short int sll_family;
    unsigned short int sll_protocol;
    int sll_ifindex;
    unsigned short int sll_hatype;
    unsigned char sll_pkttype;
    unsigned char sll_halen;
    unsigned char sll_addr[8];
  };

struct pollfd

場所:/usr/include/sys/poll.h
struct pollfd
struct pollfd
  {
    int fd;			/* File descriptor to poll.  */
    short int events;		/* Types of events poller cares about.  */
    short int revents;		/* Types of events that actually occurred.  */
  };


Tomofumi Matsuzawa