指定したネットワークインタフェースのMACアドレスを取得する
※:この記事は Crystal Advent Calendar 2018 の4日目です。
以前から,crystal-emailやcrystal-patliteなど,CrystalでTCPベースのネットワークプログラムをチョコチョコと書いていましたが,ここのところネットワーク熱が再燃しています。
直近だと,ICMP Echoを投げてPingコマンドもどきの動作をするping.crとか。
まぁ,完全に知的好奇心でチャレンジしているので,実用性については深く追求しない方向でお願いします。
で,今回はなんとなくCrystalのプログラム中で,ハードウェアに搭載されたネットワークインタフェースのMACアドレスを取得することに挑戦してみました。
現状Linuxでしか動作確認が取れていませんが(MacOS 10.14だとエラーになる),一応の形になったコードがこちら。
# get_mac.cr require "socket" lib LibC IF_NAMESIZE = 16 IFHWADDRLEN = 6 SIOCGIFHWADDR = 0x8927 # if_req構造体の簡易版 struct IfReq ifr_name : StaticArray(Char, IF_NAMESIZE) ifr_addr : Sockaddr end # ioctl関数の定義 fun ioctl(fd : Int, request : Int, value_result : Void*) : Int end unless ARGV.size == 1 STDERR << "usage: get_mac IF_NAME\n" exit(1) end if_name = ARGV.first unless if_name.size < LibC::IF_NAMESIZE STDERR << "Error: IF name too long.\n" exit(1) end # ioctl関数に渡すソケットを用意 socket = LibC.socket(LibC::AF_INET, LibC::SOCK_DGRAM, 0) # if_req構造体を生成 ifreq = LibC::IfReq.new # if_req構造体にアドレスファミリを設定 ifreq.ifr_addr.sa_family = LibC::AF_INET # if_req構造体に対象のインターフェース名を設定 i = 0 if_name.to_slice.each do |byte| ifreq.ifr_name[i] = byte i += 1 end ifreq.ifr_name[i] = 0 # ioctl関数が負の値を返してきたらエラー if LibC.ioctl(socket, LibC::SIOCGIFHWADDR, pointerof(ifreq).as(Void*)) < 0 STDERR << "Error: " << Errno.new(Errno.value) << '\n' exit(1) end # if_req構造体からMACアドレスを取り出して整形 puts mac = ifreq.ifr_addr.sa_data.to_slice[0, LibC::IFHWADDRLEN].map { |b| b.to_s(16).rjust(2, '0') }.join(':')
$ ip a : : 2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:0c:29:44:5b:f7 brd ff:ff:ff:ff:ff:ff inet 172.16.239.146/24 brd 172.16.239.255 scope global noprefixroute dynamic ens33 valid_lft 1646sec preferred_lft 1646sec inet6 fe80::c720:1500:965d:9420/64 scope link noprefixroute valid_lft forever preferred_lft forever $ crystal build get_mac.cr $ ./get_mac ens33 00:0c:29:44:5b:f7
やっていることは,LibCにioctl関数関係の定義を追加して,あとは,ガリガリとLibCの機能を叩きまくってる感じです。
Macだとioctl関数がENXIOで落ちるトカ,if_req構造体が簡易版過ぎて他の用途に使えないトカ,アラは色々ありますが,CentOS7環境では一応MACアドレスが取れてきているようです。
低レイヤの機能を触ってみるのも楽しいですね。