blank
Raw IP socket プログラミング

[概要]

Unix 系 OS では Raw IP ソケットインターフェースが用意されており、 生の IP パケットをユーザ空間で組み立てて送信する/任意のパケット を受信することができます。これを利用することによって新しいトラン スポートプロトコルなどをユーザ空間で実装することが可能になります。 Raw IP ソケットは各 OS毎に実装が若干異なるのでここでは Linux ベー スで、Raw IP socket を使って UDP/IPパケットを生成し、送信するプ ログラムを元にして解説します(受信についてはほかで解説されている ので省略します)。

[解説]

基本的な情報は man 7 ip, man 7 raw にだいたい書かれているので socket プログラミングの経験があればある程度イメージできると思い ます。Raw IP socket を利用する場合と通常の socketを利用する場合 の大きな違いは、bind(2) を用いてローカルアドレスとのバインドをし ません(できません)。パケットを送信する場合には bind(2) しないで Raw IP ソケット経由でいきなり送信します。ただし setsockopt(2) を つかってデバイスにバインドすることはできます(man 7 socket 参照の こと)。

また、Raw IP ソケットは実効 uid が 0 のプロセスもしくは CAP_NET_RAW のケーパビリティを持つプロセスのみオープン可能です。

以上をふまえるとおおまかな流れは以下のようになります。
  1. socket(2) を用いて Raw IP socket を生成
  2. setsockopt(2) を用いて Raw IP ソケットのオプションを設定
  3. IP パケット生成
  4. sendto(2) でデータ送信

また Raw IP を使う場合にはカーネルに渡すデータには IP へッダが含 めることができます。setsockopt(2) で IP_HDRINCL を設定す ることによってカーネルが IP へッダを付加することを抑制できます。 このオプションを使った場合でもいくつかの IP へッダのフィールド値 の設定をカーネルにまかせることができます。内容は以下の通りです。

+------------------------------------------------------------------+
|IP Header fields modified on sending when IP_HDRINCL is specified |
+------------------------------------------------------------------+
|  Sending fragments with IP_HDRINCL is not supported currently.   |
+--------------------------+---------------------------------------+
|IP Checksum               |Always filled in.                      |
+--------------------------+---------------------------------------+
|Source Address            |Filled in when zero.                   |
+--------------------------+---------------------------------------+
|Packet Id                 |Filled in when passed as 0.            |
+--------------------------+---------------------------------------+
|Total Length              |Always filled in.                      |
+--------------------------+---------------------------------------+

ただし、フラグメントされたパケットにはこのオプションはうまく機能 しません。

まず、手始めとして socket(2) で Raw IP socket を作成しますが、 domain は PF_INET, type は SOCK_RAW, protocol は IPPROTO_RAW を 指定します。

     int sockfd, on = 1;

     sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);

必要に応じて setsockopt(2) を用いて IP_HDRINCL を設定します(自分 で IP へッダをつけない場合には不要です)。

     if ((setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))) <0) {
        ; /* error handling */
     }

ここでは IP へッダを自分で生成することにしたので IP へッダを設定 し、次に UDP へッダを設定します。

    char packet[ETH_DATA_LEN]; 
    struct iphdr *iph;
    struct udphdr *udph;
    unsigned long  saddr, daddr;
 
    iph = (struct iphdr *)packet;

    iph->version = 4;
    iph->ihl = 5;
    iph->tos = 0;
    iph->tot_len = 0;
    iph->id = 0;
    iph->frag_off = 0;
    iph->ttl = 16;
    iph->protocol = IPPROTO_UDP;
    iph->check = 0;
    iph->saddr = saddr; 
    iph->daddr = daddr; 

    udph = (struct udphdr *)(packet + sizeof(struct iphdr));
    udph->source = htons(dport);  /* dummy */
    udph->dest = htons(dport);
    udph->len = htons(len + sizeof(struct udphdr));
    udph->check = 0;

ここまでできれば、パケットのペイロード部分を設定し、あとは通常の socket を用いたプログラミングと同じです。sendto(2) で送りたいデー タを送るだけです。

最後に、実際の UDP/IP パケットを送るプログラムをつけておきますので、 参考にしてください。

[ファイル]