class Scriptroute::Rockettrace
This is what traceroute would do, if only we could change it for the specific purpose of network mapping.
Attributes
Public Class Methods
Creates a Rockettrace
object, which wraps the results of the trace. @param destination [String] the destination, perhaps a
hostname or ip address
@param startTTL [Fixnum] what ttl to start probing at,
perhaps higher if the hops nearby are not interesting or would be probed excessively.
@param use_tcp [Boolean] whether to use TCP
packets
instead of ICMP
@param repetitions [Fixnum] how many probes to send at
each TTL, more for better detection of multipath, fewer for faster execution
@param stopTTL [Fixnum] what maximum TTL to use, in
case there is a loop.
@param reprieves [Fixnum] how many unresponsive hops to
ignore while probing a partially unresponsive path. For example, if you want it to give up after four unresponsive hops, reprieves should be four. (I think.)
# File lib/scriptroute/rockettrace.rb, line 29 def initialize(destination, startTTL=1, use_tcp=false, repetitions=3, stopTTL=64, reprieves=1) # construct the first probe packet. probe = use_tcp ? Scriptroute::TCP.new(0) : Scriptroute::UDP.new(12) # 12 bytes of udp data are needed to ensure a response probe.ip_dst = destination # causes the interpreter to lookup the destination if not an ip address already. @destination_address = probe.ip_dst @results = Hash.new { |h,k| h[k] = Array.new } @finished_because = "last ttl" @reprieves = reprieves # one more than the number of timeout responses to be tolerated. @last_ttl = 0 catch :done do ( startTTL..stopTTL ).each { |ttl| probe.ip_ttl = ttl packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,probe) ]) if(packets[0].response) then if(packets[0].response.packet.is_a?(Scriptroute::ICMP)) then if(@results.keys.detect { |e| # puts "#{@results[e][0][0]} ==? #{packets[0].response.packet.ip_src}"; @results[e][0][0] == packets[0].response.packet.ip_src }) then # we've found a loop. # append the loopy entry to the path # puts "loop detected" @results[ttl].push [ packets[0].response.packet.ip_src, packets[0].rtt, '' ] # and we're done. @last_ttl = ttl @finished_because = "loop" throw :done else # no loop. we might continue. @results[ttl].push [ packets[0].response.packet.ip_src, packets[0].rtt, '' ] # any unreach message is sufficient to stop us. if(packets[0].response.packet.icmp_type == Scriptroute::ICMP::ICMP_UNREACH) then if(packets[0].response.packet.icmp_code != Scriptroute::ICMP::ICMP_UNREACH_PORT) then @results[ttl][-1][2] = "code%d" % packets[0].response.packet.icmp_code @finished_because = "unreachable" else @finished_because = "done" end @last_ttl = ttl throw :done end end else # got a response, but not icmp. let's just assume for now # that we're running a tcp traceroute and got the tcp reset. @last_ttl = ttl @finished_because = "app" throw :done end # end received response block. else # got no response here. @results[ttl].push [ '', "*", '' ] # we want two consecutive unresponsive hops before we decide it's over. if @last_ttl == 0 @last_ttl = ttl + @reprieves elsif @last_ttl < ttl # a prior reprieve was taken. @last_ttl = ttl + @reprieves end # if reprieves is zero, fall through and finish. if(@last_ttl == ttl) then @finished_because = "unresponsive" throw :done end end } end # make certain that last_ttl is set, even if we hit the end early. if(@last_ttl == 0) then @last_ttl = stopTTL end ( 2..repetitions ).each { |rep| packets = Scriptroute::send_train((startTTL..@last_ttl).map { |ttl| nprobe = use_tcp ? Scriptroute::TCP.new(0) : Scriptroute::UDP.new(12) nprobe.ip_dst = probe.ip_dst # avoid the name lookup nprobe.ip_ttl = ttl Struct::DelayedPacket.new(0.1, nprobe) }) packets.each { |four| # four.response may be nil (?) # four.probe.packet may be nil if the packet was not seen outgoing. if(four.response && four.probe.packet) then @results[four.probe.packet.ip_ttl].push [ four.response.packet.ip_src, four.rtt, '' ] else @results[four.probe.packet.ip_ttl].push [ '', "*", '' ] end } } end
Public Instance Methods
@param [Fixnum] ttl The TTL at which to query for responses. @return [Array<IPaddress,rtt,response>] the results of all probes at a particular TTL.
# File lib/scriptroute/rockettrace.rb, line 139 def [](ttl) @results[ttl][0][0] end
Invokes the given block for each ttl and result array, as described in {#[]}
# File lib/scriptroute/rockettrace.rb, line 144 def each @results.each { |ttl,res| yield ttl, res } end
@return [Fixnum] the last TTL for which results are present (even if unresponsive)
# File lib/scriptroute/rockettrace.rb, line 128 def length return @last_ttl end
@return [Fixnum] the maximum responsive ttl detected.
# File lib/scriptroute/rockettrace.rb, line 133 def responsive_length l = ( 1.. @last_ttl ).to_a.reverse.detect { |ttl| @results[ttl][0][0] != '' } end
@param [nil,lambda] nameify optional lambda to nameify (reverse
dns lookup or other process) an IP address when printing results.
@return [String]
# File lib/scriptroute/rockettrace.rb, line 152 def to_s(nameify=nil) nameify = lambda { |x| x } if (nameify == nil) @results.sort.map { |ttl,res| lastip = ''; ttl.to_s + " " + res.map { |ip,rtt,err| if(ip != lastip) then lastip = ip if ip != '' then nameify.call(ip).to_s + " " else "" end else "" end + if(rtt == '*') then '*' else "%5.3f ms" % (rtt * 1000.0) end + if(err != '') then " " + err else "" end }.join(' ') }.join("\n") end