Class: Scriptroute::Rockettrace

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/scriptroute/rockettrace.rb

Overview

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(destination, startTTL = 1, use_tcp = false, repetitions = 3, stopTTL = 64, reprieves = 1) ⇒ Rockettrace

Creates a Rockettrace object, which wraps the results of the trace.

Parameters:

  • destination (String)

    the destination, perhaps a hostname or ip address

  • startTTL (Fixnum) (defaults to: 1)

    what ttl to start probing at, perhaps higher if the hops nearby are not interesting or would be probed excessively.

  • use_tcp (Boolean) (defaults to: false)

    whether to use TCP packets instead of ICMP

  • repetitions (Fixnum) (defaults to: 3)

    how many probes to send at each TTL, more for better detection of multipath, fewer for faster execution

  • stopTTL (Fixnum) (defaults to: 64)

    what maximum TTL to use, in case there is a loop.

  • reprieves (Fixnum) (defaults to: 1)

    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.)



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 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

Instance Attribute Details

#destination_addressObject (readonly)

Returns a value representing the address probed (perhaps if given a hostname).

Returns:

  • a value representing the address probed (perhaps if given a hostname)



9
10
11
# File 'lib/scriptroute/rockettrace.rb', line 9

def destination_address
  @destination_address
end

#finished_becauseString (readonly)

Returns an explanation of why the trace terminated.

Returns:

  • (String)

    an explanation of why the trace terminated.



8
9
10
# File 'lib/scriptroute/rockettrace.rb', line 8

def finished_because
  @finished_because
end

Instance Method Details

#[](ttl) ⇒ Array<IPaddress,rtt,response>

Returns the results of all probes at a particular TTL.

Parameters:

  • ttl (Fixnum)

    The TTL at which to query for responses.

Returns:

  • (Array<IPaddress,rtt,response>)

    the results of all probes at a particular TTL.



139
140
141
# File 'lib/scriptroute/rockettrace.rb', line 139

def [](ttl)
  @results[ttl][0][0]
end

#eachObject

Invokes the given block for each ttl and result array, as described in #[]



144
145
146
# File 'lib/scriptroute/rockettrace.rb', line 144

def each 
  @results.each { |ttl,res| yield ttl, res }
end

#lengthFixnum

Returns the last TTL for which results are present (even if unresponsive).

Returns:

  • (Fixnum)

    the last TTL for which results are present (even if unresponsive)



128
129
130
# File 'lib/scriptroute/rockettrace.rb', line 128

def length 
  return @last_ttl
end

#responsive_lengthFixnum

Returns the maximum responsive ttl detected.

Returns:

  • (Fixnum)

    the maximum responsive ttl detected.



133
134
135
# 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) ⇒ String

Parameters:

  • nameify (nil, lambda) (defaults to: nil)

    optional lambda to nameify (reverse dns lookup or other process) an IP address when printing results.

Returns:

  • (String)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# 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