bit

ネットワークインタフェイス一覧

Linuxのネットワークインタフェイス一覧を取得する話。
ifconfig コマンドの出力がパースしにくい*1ので、もう少し簡単に取得できないかと調べた。ifconfig のソースコード*2読んだだけだが、役に立つ人もいるかもしれないので書いておく。
先にオチを言っておくと、パースするほうが簡単だった…。

ifconfig は、/proc/net/dev から読み取ったインタフェイス一覧*3と、ioctl(2) の SIOCGIFCONF リクエストで取り出した情報*4をマージしている。前者には IP エイリアス情報が含まれず、後者にはアクティブなインタフェイスしか含まれないからである。
話を元に戻すと、簡単に一覧を取得するには /proc/net/dev のほうはともかく、ioctl が厄介。ifconf 構造体を ioctl 関数に渡せばよいのだが、こいつが ifreq 構造体へのポインタを持っている。

struct ifconf
  {
    int ifc_len;                        /* Size of buffer.  */
    union
      {
        __caddr_t ifcu_buf;
        struct ifreq *ifcu_req;
      } ifc_ifcu;
  };

知らない人のために書いておくと、ioctl 関数は、(領域を割り当てた)構造体のポインタを渡すと、その構造体にリクエストしたデバイス情報を埋め込んで返してくれる。
Perl の ioctl 関数もまったく同じ。つまり、Perlでバッファ確保とそのポインタを渡す必要がある…。
下がちょっと苦労しながらできたプログラム。

use Socket;
sub _TEST_get_ifnames {
  socket my $sock, PF_INET, SOCK_DGRAM, 0 or die "socket: $!";

  # ダミー文字列で、必要領域を確保。
  my $ifreqs_buf = (pack 'a16a16', 'a', 'a') x $MAX_NB_IFREQS;
  # 'iP' は構造体 ifreq の形から推測。これでポインタが $ifconf_buf に格納される。
  my $ifconf_buf = pack 'iP', (length $ifreqs_buf), $ifreqs_buf;

  ioctl $sock, $IOCTL_SIOCGIFCONF, $ifconf_buf or die "ioctl: $!";
  my $data_size = unpack 'iP', $ifconf_buf;

  my @ifnames = ();
  # $IFREQ_SIZE 値は、C で sizeof() するしかない。
  for (my $i = 0; $i < $data_size; $i += $IFREQ_SIZE) {
    my $ifreq = substr $ifreqs_buf, $i, $IFNAME_SIZE;
    push @ifnames, (unpack 'a16', $ifreq); # 'a16' は、定数 IF_NAMESIZ から推測。
  }

  close $sock;

  return \@ifnames;
}

もはや自分でも何書いているか分かんない。

*1:CPANにいろいろ転がっているのは知っている。

*2:[https://developer.berlios.de/projects/net-tools/:title=net-tools]

*3:lib/interface.c の if_readlist_proc関数を参照

*4:lib/interface.c の if_readconf関数を参照