memo.log

技術情報の雑なメモ

【Ruby】Raw Socket でICMP Echo を送信するメモ

概要

  • Ruby で Raw Socket を使ってICMP Echo をヘッダから作っていこうと思う
  • 以下画像のICMPヘッダ部分を作成する
  • 目的はRaw Socketの扱い方に慣れること
  • 2進数の取り扱いが難しかったので、とりあえずイメージしやすいように文字列型で扱う。
    • 慣れてきたらビット演算に移行する
    • 実装も適当なので参考はほどほどに

f:id:kuredev:20200412232835p:plain

コードメモ

require "socket"
require_relative "util"

socket = Socket.open(
  Socket::AF_INET,
  Socket::SOCK_RAW,
  Socket::IPPROTO_ICMP
)

# ヘッダ情報の定義
type = 0x08 # Echo Requestなので固定
code = 0x00 # Echo Requestなので固定(レスポンス時にはエラーの原因等に応じてコードが入る)
id = 0x0001# 識別子。今回は↑の図の通り1(16bit)
seq_number = 0x0016 # 上図にしたがう
data_value = 0x6162 # 上図にしたがう

# チェックサムの計算
# https://qiita.com/kure/items/fa7e665c2259375d9a81
checksum_value = checksum(type, code, id, seq_number, data_value)

# 送信するビット配列を構成する
data =
  type.to_s(2).rjust(8, "0") +
  code.to_s(2).rjust(8, "0") +
  checksum_value.to_s(2).rjust(16, "0") +
  id.to_s(2).rjust(16, "0") +
  seq_number.to_s(2).rjust(16, "0") + 
  data_value.to_s(2).rjust(16, "0")

# ASCIIコードで文字列に変換
data_byte_arr = data.scan(/.{1,#{8}}/) # 1Byteずつの配列に変換
data_byte_arr.map! { |byte| byte.to_i(2).chr }
trans_data = data_byte_arr.join

# ソケットアドレス構造体を pack した文字列
# https://docs.ruby-lang.org/ja/latest/class/Socket.html#S_PACK_SOCKADDR_IN
sockaddr = Socket.sockaddr_in(nil, "n.n.n.n") # n.n.n.n は適当なIPアドレスに読み替えること

# 送信
socket.send(trans_data, 0, sockaddr)
# 引数 num<2進数> を桁数ketanum から多かったら桁上りする
# @param num String "11001100110100011"
# @param keta_num Integer 16
def ketaagari_check(num, keta_num = 16)
  # 桁上りさせる桁数
  agaru_num = num.length - keta_num

  # 本来の桁数の値
  moto_value = num[agaru_num, keta_num]

  # 桁上げする値
  agaru_value = num[0, agaru_num]

  # 足す
  sum = moto_value.to_i(2) + agaru_value&.to_i(2)

  # ビット反転
  sum ^ 0xffff
end

# チェックサムを計算する
# @param type [Integer]
# @param code [Integer]
# @param id [Integer]
# @param seq_number [Iteger]
# @param data [Integer]
# @return [Integer]
def checksum(type, code, id, seq_number, data)
  bit_1= type.to_s(2).rjust(8, "0") + code.to_s(2).rjust(8, "0")
  bit_2 = id.to_s(2).rjust(16, "0")
  bit_3 = seq_number.to_s(2).rjust(16, "0")

  # data を16bitごとに分ける必要がある
  # https://qiita.com/paty-fakename/items/990fe9d57864054409e1
  data_arr = data.to_s(2).rjust(16, "0").scan(/.{1,#{16}}/)

  data_arr_int = data_arr.map do |data|
    data.to_i(2)
  end

  data_sum = data_arr_int.sum

  result = bit_1.to_i(2) + bit_2.to_i(2) + bit_3.to_i(2) + data_sum

  ketaagari_check(result.to_s(2).rjust(16, "0"))
end

参考

(1)メモ帳 チェックサム http://specialimpact.blog22.fc2.com/blog-entry-47.html

(2)Internet Control Message Protocol - Wikipedia https://ja.wikipedia.org/wiki/Internet_Control_Message_Protocol

(3)ICMP http://pentan.info/doc/rfc/j792.html