class Scriptroute::Rockettrace

This is what traceroute would do, if only we could change it for the specific purpose of network mapping.

Attributes

destination_address[R]
finished_because[R]

Public Class Methods

new(destination, startTTL=1, use_tcp=false, repetitions=3, stopTTL=64, reprieves=1) click to toggle source

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

[](ttl) click to toggle source

@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
each() { |ttl, res| ... } click to toggle source

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
length() click to toggle source

@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
responsive_length() click to toggle source

@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
to_s(nameify=nil) click to toggle source

@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