メモ
- 原理的には以下ページの通りに行う。
setsockopt
を呼び出して、その時にPACKET_ADD_MEMBERSHIP
オプションとそのオプションに必要なstruct packet_mreq
のデータを渡せば良い。
Man page of PACKET -
struct packet_mreq
はCの構造体であるが、参考リンクの通り色々と調べたところ、Rubyから渡す場合は構造体のメンバのデータを順番に並べた文字列を渡せば良さそう。 - 例えば、
mr_ifindex
はインターフェース番号(int=4Byte)なので、Rubyの文字列の一文字のデータはCのchar1つに変換されるよう(動作的にそう見えるが厳密には違うかも)なので、インターフェース番号を加えた4文字の文字列を渡せば良い。下記のmr_ifindex
メソッドであれば、例えば、インターフェース番号が1
だったら"1000"
が返る。 -
ETH_P_ALL
はとりあえず全てのプロトコルを対象とした番号(0x0003)をネットワークバイトオーダーに変換(htons)した値を指定。 - 実行には root 権限が必要。
サンプルコード
require "socket"
SOL_PACKET = 0x0107 # bits/socket.h
PACKET_ADD_MEMBERSHIP = 0x0001 # netpacket/packet.h
PACKET_MR_PROMISC = 0x0001 # netpacket/packet.h
ETH_P_ALL = 768 # htons(ETH_P_ALL), linux/if_ether.h
# @return [Integer]
def if_name_to_index(if_name)
Socket.getifaddrs.find do |ifaddr|
ifaddr.name == if_name
end&.ifindex
end
# int(4Byte)
#
# @return [String]
def mr_ifindex(if_name)
[[if_name_to_index(if_name)].pack("c")].pack("a4")
end
# unsigned short(2Byte)
#
# @return [String]
def mr_type
[PACKET_MR_PROMISC].pack("S")
end
# unsigned short(2Byte)
#
# @return [String]
def mr_alen
[0].pack("S")
end
# unsigned short(1Byte * 8)
#
# @return [String]
def mr_address
[0].pack("C")*8
end
# https://linuxjm.osdn.jp/html/LDP_man-pages/man7/packet.7.html
# struct packet_mreq {
# int mr_ifindex; /* インターフェース番号 */ 4Byte
# unsigned short mr_type; /* 動作 */ 2Byte
# unsigned short mr_alen; /* アドレスの長さ */ 2Byte
# unsigned char mr_address[8]; /* 物理層のアドレス */ 1Byte * 8
# };
#
# @return [String]
def mreq(interface)
mr_ifindex(interface) + mr_type + mr_alen + mr_address
end
mreq = mreq("eth0")
sock = Socket.new(Socket::AF_PACKET, Socket::SOCK_RAW, ETH_P_ALL)
sock.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, mreq)
参考
Ruby Raw Socket on Linux (ruby 1.9.3, linux x86_64) https://gist.github.com/k-sone/8036832