memo.log

技術情報の雑なメモ

setsocopt探訪のメモ(1)

ソケットをプロミスキャスモードにするためにはPACKET_ADD_MEMBERSHIPオプションをsetsockoptで設定する必要がある。

よくあるサンプルコードだが、Cなら以下のような感じだ。

  sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  struct packet_mreq mreq;
  mreq.mr_ifindex = 2; // eth0
  mreq.mr_type = PACKET_MR_PROMISC; // 1
  mreq.mr_alen = 0;
  mreq.mr_address[0] ='\0';
  setsockopt(sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, mreq2, sizeof(mreq2));

packet_mreq というのは、 PACKET_ADD_MEMBERSHIPオプションのページに説明があり、このオプションで使用する構造体だ。構造は以下の通り。この動作( mr_type )に PACKET_MR_PROMISC を設定することでプロミスキャスモードにできる。

struct packet_mreq {
    int            mr_ifindex;    /* インターフェース番号 */
    unsigned short mr_type;       /* 動作 */
    unsigned short mr_alen;       /* アドレスの長さ */
    unsigned char  mr_address[8]; /* 物理層のアドレス */
};

さて、これはRubyでも同様で、BasicSocke#setsockoptメソッドがあり、 https://github.com/kuredev/simple_bridge でもそのように実装した(ネット上の情報を参考に構成)。

simple_bridge/bridge.rb at a92bfd9f5bb1919fec3af350f310215a6b06d402 · kuredev/simple_bridge · GitHub

  PACKET_ADD_MEMBERSHIP = 0x0001 # netpacket/packet.h
....
  def promiscuous(socket, interface)
    ifreq = [interface].pack('a' + IFREQ_SIZE.to_s)
    socket.ioctl(SIOCGIFINDEX, ifreq)
    if_num = ifreq[Socket::IFNAMSIZ, IFINDEX_SIZE]
    mreq = if_num.dup
    mreq << [PACKET_MR_PROMISC].pack('s')
    mreq << ("\x00" * (PACKET_MREQ_SIZE - mreq.length))
    socket.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, mreq)
  end

さて、この setsocopt であるが、設定値が整数値や真偽値の場合は単純で良いのだが、上記のようにプロミスキャスモードを設定するとなると、低レイヤのレベルでは packet_mreq 構造体を渡すのが作法というか仕様となっている。 ところが、上記Rubyコードでは文字列を指定している。これが packet_mreq にどう対応しているのか、分からなかった。というか、なぜ、文字列で指定すると動作するのかよく分からなかった。 雰囲気的には構造体の上の要素から順番に文字列にしていけばよいのだろうが、なぜこれで動作するのか…。そもそもそれで良いのか。

たしかにRubyのドキュメントを読んでも、setsocopt の optval の値は以下のように説明されている。

# より複雑な場合
optval = IPAddr.new("224.0.0.251").hton +
         IPAddr.new(Socket::INADDR_ANY, Socket::AF_INET).hton
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, optval)

IP_ADD_MEMBERSHIP というオプションはこちらのページの解説等を読むと、どうやらマルチキャストに対応するためのオプションのようで本来必要な設定値は以下の構造体のようだ。

struct ip_mreq {
    struct in_addr imr_multiaddr;   /* multicast group to join */
    struct in_addr imr_interface;   /* interface to join on */
}

in_addrsockaddr_in構造体 によると、

  struct in_addr {
      u_int32_t s_addr;
   };

であり、 u_int32_tuint32_t ‐ 通信用語の基礎知識 によると、 32ビット長の無符号整数である。

IPAddr.new("224.0.0.251").hton というのはデータとしては "\xE0\x00\x00\xFB" となる。中身的には8桁の16進数となっており、16進数1文字 = 4bit なので、 8文字×4bit で32bitで長さ的にも整合する。

とはいえ、 より複雑な場合 と言われても、C言語の構造体と Ruby のIPAddr.new.htonが整合するなんてピンと来ないし、他のオプションでは他の構造体の可能性もあるので、原則的な仕様が知りたいところだ。 というか、なぜ、Rubyから文字列を渡してCの構造体として解釈される(?)のかが不明だ。 上記Rubyドキュメントでは 文字列の場合には setsockopt(2) にはその文字列と長さが渡されます。 とあり、どうやらCには文字列として渡されているようだ。

長くなってきたので、続きは次回に。