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

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

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

今回は、tracerouteと同じような機能を持つscanrouteというプログラムを作ってみた。引数で渡されたホストまでのルータを表示する。Googleなんか指定すると結果が返ってくるのに、Yahooを指定すると途中で止まってしまう。何でだろう?原因不明。『基礎からわかるTCP/IPネットワーク実験プログラム』に載ってるやつのRuby版なだけだったり。rootでないと動かない、またLinux以外で動くか不明。以下ソース

scanroute.rb

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
            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)
        puts " #{ip_header.ip_src}"
        if(icmp.icmp_type == ICMP::UNREACH)
            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"
            end

            break
        end
    end
end

send_sd.close()
recv_sd.close()

ip.rb

class IPHeader
    attr_accessor :version
    attr_accessor :ip_hl
    attr_accessor :ip_tos
    attr_accessor :ip_len
    attr_accessor :ip_id
    attr_accessor :ip_off 
    attr_accessor :ip_ttl
    attr_accessor :ip_p
    attr_accessor :ip_sum
    attr_accessor :ip_src
    attr_accessor :ip_dst

    def initialize(packet = nil)
        if(packet == nil)
            return
        end

        @version = (packet[0] >> 4) & 0x0F
        @ip_hl = packet[0] & 0x0F
        @ip_tos = packet[1]
        @ip_len = (packet[2] << 8) + packet[3]
        @ip_id = (packet[4] << 8) + packet[5]
        @ip_off = (packet[6] << 8) + packet[7]
        @ip_ttl = packet[8]
        @ip_p = packet[9]
        @ip_sum = (packet[10] << 8) + packet[11]
        @ip_src = ip_to_s(packet, 12)
        @ip_dst = ip_to_s(packet, 16)
    end

    def to_byte_array()
        array = "                    "
        array[0] = ((@version << 4) + @ip_hl) & 0xFF
        array[1] = @ip_tos & 0xFF
        array[2] = (@ip_len >> 8) & 0xFF
        array[3] = @ip_len & 0xFF
        array[4] = (@ip_id >> 8) & 0xFF
        array[5] = @ip_id & 0xFF
        array[6] = (@ip_off >> 8) & 0xFF
        array[7] = @ip_off & 0xFF
        array[8] = @ip_ttl & 0xFF
        array[9] = @ip_p & 0xFF
        array[10] = (@ip_sum >> 8) & 0xFF
        array[11] = @ip_sum & 0xFF
        s_to_ip(array, 12, @ip_src)
        s_to_ip(array, 16, @ip_dst)

        return array
    end

    def s_to_ip(array, index, str_ip)
        split = str_ip.split('.')
        for i in 0..3
            array[index + i] = split[i].to_i() & 0xFF
        end
    end

    def ip_to_s(packet, index)
        return sprintf("%d.%d.%d.%d", packet[index], packet[index + 1], packet[index + 2], packet[index + 3])
    end
end

udp.rb

class UDPHeader
    attr_accessor :uh_sport
    attr_accessor :uh_dport
    attr_accessor :uh_ulen
    attr_accessor :uh_sum

    def initialize(packet = nil)
        if(packet == nil)
            return
        end

        @uh_sport = (packet[0] << 8) + packet[1]
        @uh_dport = (packet[2] << 8) + packet[3]
        @uh_ulen = (packet[4] << 8) + packet[5]
        @uh_sum = (packet[6] << 8) + packet[7]
    end

    def to_byte_array()
        array = "        "
        array[0] = (@uh_sport >> 8) & 0xFF
        array[1] = @uh_sport & 0xFF
        array[2] = (@uh_dport >> 8) & 0xFF
        array[3] = @uh_dport & 0xFF
        array[4] = (@uh_ulen >> 8) & 0xFF
        array[5] = @uh_ulen & 0xFF
        array[6] = (@uh_sum >> 8) & 0xFF
        array[7] = @uh_sum & 0xFF

        return array
    end
end

icmp.rb

class ICMP
    attr_reader :icmp_type
    attr_reader :icmp_code
    attr_reader :icmp_cksum

    ECHOREPLY      = 0
    UNREACH        = 3
    SOURCEQUENCH   = 4
    REDIRECT       = 5
    ECHO           = 8
    ROUTERADVERT   = 9
    ROUTERSOLICIT  = 10
    TIMXCEED       = 11
    PARAMPROB      = 12
    TSTAMP         = 13
    TSTAMPREPLY    = 14
    IREQ           = 15
    IREQREPLY      = 16
    MASKREQ        = 17
    MASKREPLY      = 18

    UNREACH_NET             = 0
    UNREACH_HOST            = 1
    UNREACH_PROTOCOL        = 2
    UNREACH_PORT            = 3
    UNREACH_NEEDFRAG        = 4
    UNREACH_SRCFAIL         = 5
    UNREACH_NET_UNKNOWN     = 6
    UNREACH_HOST_UNKNOWN    = 7
    UNREACH_ISOLATED        = 8
    UNREACH_NET_PROHIB      = 9
    UNREACH_HOST_PROHIB     = 10
    UNREACH_TOSNET          = 11
    UNREACH_TOSHOST         = 12
    UNREACH_FILTER_PROHIB   = 13
    UNREACH_HOST_PRECEDENCE = 14

    REDIRECT_NET     = 0
    REDIRECT_HOST    = 1
    REDIRECT_TOSNET  = 2
    REDIRECT_TOSHOST = 3

    TIMXCEED_INTRANS = 0
    TIMXCEED_REASS   = 1

    PARAMPROB_ERRATPTR  = 0
    PARAMPROB_OPTABSENT = 1
    PARAMPROB_LENGTH    = 2

    def initialize(packet)
        @icmp_type = packet[0]
        @icmp_code = packet[1]
        @icmp_cksum = (packet[2] << 8) + packet[3]
    end
end