« SSHKeychain | Main | IP/TCP/UDPのチェックサム »

2007年2月18日

SOCK_RAWとバイトオーダ

[ カテゴリ: NetBSD ]

たまにはそれっぽい記事も書いてみよう。 ユーザランドでIPデータグラムを生成して、SOCK_RAWではき出すときの注意点です。 ちなみにIPv4です。IPv6は必要に迫られてないので見てません。 また、環境はNetBSDしか見てないので、特にLinux系だと事情が違うかもしれないです。

まずはコードから。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>

int
main(void)
{
  struct in_addr ip_src = { .s_addr = htonl(0x0a000001) }; /* 10.0.0.1 */
  struct in_addr ip_dst = { .s_addr = htonl(0x0a000002) }; /* 10.0.0.2 */
  struct sockaddr_in sa_dst;
  struct ip ip;

  int fd;
  int on = 1;
  size_t len;

  if ((fd = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
    perror("socket");
    exit(1);
  }
  if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(int)) < 0) {
    perror("setsockopt");
    exit(1);
  }

  memset(&ip, 0, sizeof(struct ip));
  ip.ip_v = IPVERSION;
  ip.ip_hl = sizeof(struct ip) >> 2;
  ip.ip_tos = 0;
  ip.ip_len = sizeof(struct ip);
  ip.ip_id = 0;
  ip.ip_off = IP_DF|0;
  ip.ip_ttl = 255;
  ip.ip_p = IPPROTO_RAW;
  ip.ip_sum = 0;
  memcpy(&ip.ip_src, &ip_src, sizeof(struct in_addr));
  memcpy(&ip.ip_dst, &ip_dst, sizeof(struct in_addr));

  sa_dst.sin_family = AF_INET;
  sa_dst.sin_len = sizeof(struct sockaddr_in);
  sa_dst.sin_port = htons(0);
  memcpy(&sa_dst.sin_addr, &ip_dst, sizeof(struct sockaddr_in));

  len = sendto(fd, &ip, sizeof(struct ip), 0,
           (struct sockaddr *)&sa_dst, sizeof(struct sockaddr_in));
  if (len < 0) {
    perror("sendto");
    exit(1);
  }

  return 0;
}

socketをSOCK_RAW, IPPROTO_RAWで開くのはまあいいですね。 IPヘッダもユーザランド側で作る場合は、IP_HDRINCLをセットする必要がありますが、 これも難しくはないでしょう。

問題はIPヘッダを作るところです。 普通に値を埋めているように見えますが、鋭い人なら「ん?」と思うかもしれません。 よく見ると、ip_lenとip_offにそのまま値をつっこんでいます。 ここは16ビット幅を持つフィールドなので、バイトオーダを直さないといけないはずです。 ところが、カーネル側ではこのふたつのフィールドに対して、ホスト・バイトオーダを期待しているのです (少なくとも、NetBSD 3.1_STABLEでは)。 というわけで、気を利かせてhtonsするとはまります。 ip_lenがおかしいとsendtoでEINVALが返ってきますし、ip_offに至っては何も云わずににデータグラムが出て行きます。

残りのフィールドは特に気をつけることはありません。 ip_idは自前でつけることもできますが、0でつっこむとrip_outputでよしなにつけてくれます。 またip_sumも自前で計算してもよいのですが、0でつっこむとip_outputで計算して埋めてくれます。 いずれのフィールドもネットワーク・バイトオーダです (ip_sumにバイトオーダもなんもないけれど)。 ただし、ip_sumを自前で計算するときは上記のip_lenとip_offがネットワーク・バイトオーダでなければなりませんから、注意が必要です。

Comments

Post a comment




Remember Me?