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を使います。
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 */
};
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 の組み合わせです。
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