前回の特定のホストまでのルータを表示してみるではICMPソケットの読み込みでブロッキングしてしまい返事が返ってこなかったときの処理ができなかったけど、今回はIO::selectを使うことで解決した。というか、forループで回してるところは、selectを使わないと1回しか実行されないまま終わってしまうので、前回のforループの部分は意味なかった。今回のスクリプトでやっと意味を持つようになった。ip.rb、udp.rb、icmp.rbは前回と同じなので省略。相変わらずrootでないと実行できない、そしてLinux以外で動くか不明。以下ソース
require 'socket' require './ip' require './udp' require './icmp' def make_ip_header(src_ip, dst_ip, iplen) ip = IPHeader.new ip.version = 4 ip.ip_tos = 0 ip.ip_hl = 5 ip.ip_id = 0 ip.ip_len = iplen ip.ip_off = 0 ip.ip_ttl = 64 ip.ip_p = Socket::IPPROTO_UDP ip.ip_src = src_ip ip.ip_dst = dst_ip ip.ip_sum = 0 return ip.to_byte_array() end def make_udp_header udp = UDPHeader.new udp.uh_sport = 33434 udp.uh_dport = 33434 udp.uh_ulen = 8 udp.uh_sum = 0 return udp.to_byte_array end if ARGV.size() < 1 puts 'usage ruby scanroute.rb dst_ip' exit(1) end send_sa = Socket.sockaddr_in(0, ARGV[0]) send_sd = Socket.open(Socket::AF_INET, Socket::SOCK_RAW, 255) recv_sd = Socket.open(Socket::AF_INET, Socket::SOCK_RAW, Socket::IPPROTO_ICMP) host = Socket.gethostbyname(ARGV[0])[3] ip_addr = sprintf("%d.%d.%d.%d", host[0], host[1], host[2], host[3]) packet = make_ip_header("0.0.0.0", ip_addr, 28) packet << make_udp_header() puts "scanroute #{ip_addr}" for ttl in 1..64 print ttl packet[8] = ttl & 0xFF i = 0 ip_header = nil icmp = nil for i in 0..3 if i == 3 break end break_flag = false send_sd.send(packet, 0, send_sa) while true ret = IO::select([recv_sd], nil, nil, 1.0) if ret == nil || ret[0].size() == 0 print ' ?' break; end buff, sockaddr = recv_sd.recvfrom(8192) ip_header = IPHeader.new(buff) buff.slice!(0..(ip_header.ip_hl << 2) - 1) icmp = ICMP.new(buff) if((icmp.icmp_type == ICMP::TIMXCEED && icmp.icmp_code == ICMP::TIMXCEED_INTRANS) || icmp.icmp_type == ICMP::UNREACH) break_flag = true break end end if break_flag break end end if(i < 3) print " #{ip_header.ip_src}" if(icmp.icmp_type == ICMP::UNREACH) puts if(icmp.icmp_code == ICMP::UNREACH_PORT) puts "Reach!" elsif(icmp.icmp_code == ICMP::UNREACH_HOST) puts "Host Unreachable" elsif(icmp.icmp_code == ICMP::UNREACH_NET) puts "Network Unreachable" else puts "icmo_code = #{icmp.icmp_code}" end break end end puts end send_sd.close() recv_sd.close()