#!/usr/bin/env ruby require 'icmp' require 'getopts' include Socket::Constants include ICMPModule IP_TTL = 4 class UDP < String def self.new(s) super(s); end def uh_sport; self[0..1].unpack("n")[0]; end def uh_dport; self[2..3].unpack("n")[0]; end def uh_ulen; self[4..5].unpack("n")[0]; end def uh_sum; self[6..7].unpack("n")[0]; end end def traceroute(host, port, nprobes, max_ttl, wait_time) icmp_sock = ICMPSocket.new udp_sock = UDPSocket.new ident = $$ | 0x8000 udp_sock.bind(0, ident) ttl = 0 ai = Socket::getaddrinfo(host, nil, Socket::AF_INET)[0] dst = ai[3] reached = false while not reached && ttl < max_ttl ttl += 1 printf "%3d ", ttl udp_sock.setsockopt(IPPROTO_IP, IP_TTL, ttl) reached = false probed = false nprobes.times{|i| send_time = Time.now udp_sock.send("\0\0\0\0", 0, dst, port) unless ary = select([icmp_sock], nil, nil, wait_time) print(" *") next end buf = icmp_sock.recv(65535) recv_time = Time.now iph, icmpp = ICMPModule::split(buf) # IP header and ICMP packet ipp = icmpp.icmp_ip # original IP packet udph = UDP.new(ipp.body) # original UDP header retry if ipp.ip_p != Socket::IPPROTO_UDP retry if ipp.ip_dst != dst || udph.uh_sport != ident if !probed && !reached ai0 = Socket::getaddrinfo(iph.ip_src, nil, Socket::AF_INET)[0] printf("%s (%s)", ai0[2], iph.ip_src) end printf " %.3f ms", (recv_time.to_f - send_time.to_f) * 1000 case icmpp.icmp_type when ICMP_TIMXCEED probed = true when ICMP_UNREACH case icmpp.icmp_code when ICMP_UNREACH_PORT; reached = true when ICMP_UNREACH_HOST; print(" !H") when ICMP_UNREACH_NET; print(" !N") when ICMP_UNREACH_PROTOCOL; print(" !P") end else print(" ??") end } print "\n" end end ## ## Main routine ## getopts nil, "p:33434", "q:3", "m:30", "w:3" udp_port = $OPT_p.to_i nprobes = $OPT_q.to_i max_ttl = $OPT_m.to_i wait_time = $OPT_w.to_i $stdout.sync = true traceroute(ARGV[0], udp_port, nprobes, max_ttl, wait_time)