電柱日報

日々の由無し事

指定したネットワークインタフェースのMACアドレスを取得する

※:この記事は Crystal Advent Calendar 2018 の4日目です。

以前から,crystal-emailcrystal-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アドレスが取れてきているようです。

低レイヤの機能を触ってみるのも楽しいですね。