マイペースなプログラミング日記

x86エミュレータやFPGA、WebGLにお熱なd-kamiがマイペースに書くブログ

特定のホストまでのルータを表示してみる その2

前回の特定のホストまでのルータを表示してみるでは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()