Ruby で Linux の NETLINKを使う

Ruby で Linux の NETLINKを使ってみました。

例としてネットワークインターフェースのアドレスを取得するコードを書きました。

NETLINKを使う場合は、地道にバイナリのエンコードとデコードを書く必要がありますね。

# socket生成

NETLINKのデータ送受信で使うsocketを生成します。
RubyではAF_NETLINKとNETLINK_ROUTEが定義されていないのでヘッダファイルを見て定義します。

```
require 'socket'

module Linux
  NETLINK_ROUTE    = 0
end

class Socket
  PF_NETLINK    = 16    unless defined? Socket::PF_NETLINK
  AF_NETLINK    = PF_NETLINK  unless defined? Socket::AF_NETLINK
end

s = Socket.new(Socket::AF_NETLINK, Socket::SOCK_DGRAM, Linux::NETLINK_ROUTE)
```

# リクエスト生成

netlinkでは、指定されたバイナリフォーマットを使ってリクエストを送受信します。
netlinkはstruct nlmsghdrを持ちます。
nlmsg_typeに、リクエストのタイプを指定します。IPアドレスの情報を取得するためにはGETADDRを設定します。
GETADDRの場合はstruct ifaddrmsgを使います。


```c:/usr/include/linux/netlink.h
struct nlmsghdr {
        __u32           nlmsg_len;      /* Length of message including header */
        __u16           nlmsg_type;     /* Message content */
        __u16           nlmsg_flags;    /* Additional flags */
        __u32           nlmsg_seq;      /* Sequence number */
        __u32           nlmsg_pid;      /* Sending process port ID */
};
```

```c:/usr/include/linux/if_addr.h 
struct ifaddrmsg {
        __u8            ifa_family;
        __u8            ifa_prefixlen;  /* The prefix length            */
        __u8            ifa_flags;      /* Flags                        */
        __u8            ifa_scope;      /* Address scope                */
        __u32           ifa_index;      /* Link index                   */
};
```

```
  struct {
    struct nlmsghdr nh;
    struct ifaddrmsg ifa;
  } request;
```

この構造体をpackを使ってバイナリにエンコードします。

```
module Linux
  RTM_GETADDR    = 22
  NLM_F_REQUEST    = 1
  NLM_F_ROOT    = 0x100
end

hdr = [24, Linux::RTM_GETADDR, Linux::NLM_F_REQUEST | Linux::NLM_F_ROOT, 0, 0].pack("ISSII")
ifa = [Socket::AF_INET, 0, 0, 0, 0].pack("CCCCI")
```

# リクエスト送信、レスポンス受信

先ほど作ったバイナリデータをsendで送信し、カーネルからの応答をrecvで受信します。

```
s.send(hdr+ifa, 0)
resp = s.recv(4096)
```

# レスポンス解析

レスポンスもバイナリデータです。
GETADDRのレスポンスは、struct nlmsghdr と struct ifaddrmsg　と struct rtattr　の組み合わせです。

```c:/usr/include/linux/rtnetlink.h
struct rtattr {
        unsigned short  rta_len;
        unsigned short  rta_type;
};
```
nlmsghdrとifaddrmsgをデコードします。ここまでは固定長です。
残っている部分はrtattrと可変長データなので順次デコードします。
データは4バイトアラインされています。

```
  resp_len = resp.length
  printf("resp_len:%d\n", resp_len)

  while resp_len > 0

    nlhdr_len, nlhdr_type = resp.slice!(0, 16).unpack("ISSII")
    resp_len -= nlhdr_len
    printf("len:%d type:%d ",nlhdr_len, nlhdr_type)
    ifa = resp.slice!(0, 8).unpack("CCCCI")
    printf("family:%d prefixlen:%d flags:%d scope:%d index:%d\n",ifa[0], ifa[1], ifa[2], ifa[3], ifa[4])

    len = nlhdr_len - 24

    while len > 0
      rta_len, rta_type = resp.slice!(0, 4).unpack("SS")
      rta_align_len = 4 * ((rta_len + 3) / 4)
      data_len = rta_align_len - 4
      if rta_type == 3
        data = resp.slice!(0, data_len)
      else
        data = resp.slice!(0, data_len).unpack("C*")
      end
      printf("  len:%2d type:%d data:%s\n", rta_len, rta_type, data.to_s)
      len -= rta_align_len
    end
  end
```

# 実行例

```
resp_len:220
len:68 type:20 family:2 prefixlen:8 flags:128 scope:254 index:1
  len: 8 type:1 data:[127, 0, 0, 1]
  len: 8 type:2 data:[127, 0, 0, 1]
  len: 7 type:3 data:lo
  len:20 type:6 data:[255, 255, 255, 255, 255, 255, 255, 255, 155, 9, 0, 0, 155, 9, 0, 0]
len:80 type:20 family:2 prefixlen:24 flags:128 scope:0 index:2
  len: 8 type:1 data:[192, 168, 1, 8]
  len: 8 type:2 data:[192, 168, 1, 8]
  len: 8 type:4 data:[192, 168, 1, 255]
  len: 9 type:3 data:eth0
  len:20 type:6 data:[255, 255, 255, 255, 255, 255, 255, 255, 188, 9, 0, 0, 188, 9, 0, 0]
len:72 type:20 family:2 prefixlen:16 flags:128 scope:0 index:3
  len: 8 type:1 data:[172, 17, 42, 1]
  len: 8 type:2 data:[172, 17, 42, 1]
  len:12 type:3 data:docker0
  len:20 type:6 data:[255, 255, 255, 255, 255, 255, 255, 255, 71, 15, 0, 0, 71, 15, 0, 0]
```

# 参考
* 「Linuxネットワークプログラミング」のサンプルコード 5-14 IPアドレスの取得http://www.geekpage.jp/programming/linux-network/book/05/5-14.php
* https://github.com/BytemarkHosting/netlinkrb
* https://github.com/kleymenus/netlink