scriptroute 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/sr-ally +21 -0
- data/bin/sr-liveness +11 -0
- data/bin/sr-ping-T +69 -0
- data/bin/sr-rockettrace +51 -0
- data/bin/sr-traceroute +35 -0
- data/bin/tulip +183 -0
- data/lib/scriptroute.rb +327 -0
- data/lib/scriptroute/ally.rb +449 -0
- data/lib/scriptroute/commando.rb +228 -0
- data/lib/scriptroute/fixclock +1260 -0
- data/lib/scriptroute/liveness.rb +129 -0
- data/lib/scriptroute/nameify.rb +127 -0
- data/lib/scriptroute/packets.rb +800 -0
- data/lib/scriptroute/rockettrace.rb +181 -0
- data/lib/scriptroute/tulip/helper.rb +730 -0
- data/lib/scriptroute/tulip/loss.rb +145 -0
- data/lib/scriptroute/tulip/queuing.rb +248 -0
- data/lib/scriptroute/tulip/reordering.rb +129 -0
- data/test/test_bins.rb +20 -0
- data/test/test_scriptroute.rb +155 -0
- metadata +71 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
module Scriptroute
|
2
|
+
|
3
|
+
# This is what traceroute would do, if only we could
|
4
|
+
# change it for the specific purpose of network mapping.
|
5
|
+
class Rockettrace
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
attr_reader :finished_because # @return [String] an explanation of why the trace terminated.
|
9
|
+
attr_reader :destination_address # @return a value representing the address probed (perhaps if given a hostname)
|
10
|
+
|
11
|
+
# Creates a Rockettrace object, which wraps the results
|
12
|
+
# of the trace.
|
13
|
+
# @param destination [String] the destination, perhaps a
|
14
|
+
# hostname or ip address
|
15
|
+
# @param startTTL [Fixnum] what ttl to start probing at,
|
16
|
+
# perhaps higher if the hops nearby are not interesting or
|
17
|
+
# would be probed excessively.
|
18
|
+
# @param use_tcp [Boolean] whether to use TCP packets
|
19
|
+
# instead of ICMP
|
20
|
+
# @param repetitions [Fixnum] how many probes to send at
|
21
|
+
# each TTL, more for better detection of multipath,
|
22
|
+
# fewer for faster execution
|
23
|
+
# @param stopTTL [Fixnum] what maximum TTL to use, in
|
24
|
+
# case there is a loop.
|
25
|
+
# @param reprieves [Fixnum] how many unresponsive hops to
|
26
|
+
# ignore while probing a partially unresponsive path.
|
27
|
+
# For example, if you want it to give up after four unresponsive
|
28
|
+
# hops, reprieves should be four. (I think.)
|
29
|
+
def initialize(destination, startTTL=1, use_tcp=false, repetitions=3, stopTTL=64, reprieves=1)
|
30
|
+
# construct the first probe packet.
|
31
|
+
probe = use_tcp ? Scriptroute::TCP.new(0) : Scriptroute::UDP.new(12)
|
32
|
+
# 12 bytes of udp data are needed to ensure a response
|
33
|
+
|
34
|
+
probe.ip_dst = destination # causes the interpreter to lookup the destination if not an ip address already.
|
35
|
+
|
36
|
+
@destination_address = probe.ip_dst
|
37
|
+
@results = Hash.new { |h,k| h[k] = Array.new }
|
38
|
+
@finished_because = "last ttl"
|
39
|
+
@reprieves = reprieves # one more than the number of timeout responses to be tolerated.
|
40
|
+
|
41
|
+
@last_ttl = 0
|
42
|
+
catch :done do
|
43
|
+
( startTTL..stopTTL ).each { |ttl|
|
44
|
+
probe.ip_ttl = ttl
|
45
|
+
packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,probe) ])
|
46
|
+
if(packets[0].response) then
|
47
|
+
if(packets[0].response.packet.is_a?(Scriptroute::ICMP)) then
|
48
|
+
if(@results.keys.detect { |e| # puts "#{@results[e][0][0]} ==? #{packets[0].response.packet.ip_src}";
|
49
|
+
@results[e][0][0] == packets[0].response.packet.ip_src }) then
|
50
|
+
# we've found a loop.
|
51
|
+
# append the loopy entry to the path
|
52
|
+
# puts "loop detected"
|
53
|
+
@results[ttl].push [ packets[0].response.packet.ip_src,
|
54
|
+
packets[0].rtt, '' ]
|
55
|
+
# and we're done.
|
56
|
+
@last_ttl = ttl
|
57
|
+
@finished_because = "loop"
|
58
|
+
throw :done
|
59
|
+
else
|
60
|
+
# no loop. we might continue.
|
61
|
+
@results[ttl].push [ packets[0].response.packet.ip_src,
|
62
|
+
packets[0].rtt, '' ]
|
63
|
+
# any unreach message is sufficient to stop us.
|
64
|
+
if(packets[0].response.packet.icmp_type == Scriptroute::ICMP::ICMP_UNREACH) then
|
65
|
+
if(packets[0].response.packet.icmp_code != Scriptroute::ICMP::ICMP_UNREACH_PORT) then
|
66
|
+
@results[ttl][-1][2] = "code%d" % packets[0].response.packet.icmp_code
|
67
|
+
@finished_because = "unreachable"
|
68
|
+
else
|
69
|
+
@finished_because = "done"
|
70
|
+
end
|
71
|
+
@last_ttl = ttl
|
72
|
+
throw :done
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
# got a response, but not icmp. let's just assume for now
|
77
|
+
# that we're running a tcp traceroute and got the tcp reset.
|
78
|
+
@last_ttl = ttl
|
79
|
+
@finished_because = "app"
|
80
|
+
throw :done
|
81
|
+
end # end received response block.
|
82
|
+
else
|
83
|
+
# got no response here.
|
84
|
+
@results[ttl].push [ '', "*", '' ]
|
85
|
+
# we want two consecutive unresponsive hops before we decide it's over.
|
86
|
+
if @last_ttl == 0
|
87
|
+
@last_ttl = ttl + @reprieves
|
88
|
+
elsif @last_ttl < ttl
|
89
|
+
# a prior reprieve was taken.
|
90
|
+
@last_ttl = ttl + @reprieves
|
91
|
+
end
|
92
|
+
# if reprieves is zero, fall through and finish.
|
93
|
+
if(@last_ttl == ttl) then
|
94
|
+
@finished_because = "unresponsive"
|
95
|
+
throw :done
|
96
|
+
end
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# make certain that last_ttl is set, even if we hit the end early.
|
102
|
+
if(@last_ttl == 0) then
|
103
|
+
@last_ttl = stopTTL
|
104
|
+
end
|
105
|
+
|
106
|
+
( 2..repetitions ).each { |rep|
|
107
|
+
packets =
|
108
|
+
Scriptroute::send_train((startTTL..@last_ttl).map { |ttl|
|
109
|
+
nprobe = use_tcp ? Scriptroute::TCP.new(0) : Scriptroute::UDP.new(12)
|
110
|
+
nprobe.ip_dst = probe.ip_dst # avoid the name lookup
|
111
|
+
nprobe.ip_ttl = ttl
|
112
|
+
Struct::DelayedPacket.new(0.1, nprobe)
|
113
|
+
})
|
114
|
+
packets.each { |four|
|
115
|
+
# four.response may be nil (?)
|
116
|
+
# four.probe.packet may be nil if the packet was not seen outgoing.
|
117
|
+
if(four.response && four.probe.packet) then
|
118
|
+
@results[four.probe.packet.ip_ttl].push [ four.response.packet.ip_src,
|
119
|
+
four.rtt, '' ]
|
120
|
+
else
|
121
|
+
@results[four.probe.packet.ip_ttl].push [ '', "*", '' ]
|
122
|
+
end
|
123
|
+
}
|
124
|
+
}
|
125
|
+
end # initialize
|
126
|
+
|
127
|
+
# @return [Fixnum] the last TTL for which results are present (even if unresponsive)
|
128
|
+
def length
|
129
|
+
return @last_ttl
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [Fixnum] the maximum responsive ttl detected.
|
133
|
+
def responsive_length
|
134
|
+
l = ( 1.. @last_ttl ).to_a.reverse.detect { |ttl| @results[ttl][0][0] != '' }
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param [Fixnum] ttl The TTL at which to query for responses.
|
138
|
+
# @return [Array<IPaddress,rtt,response>] the results of all probes at a particular TTL.
|
139
|
+
def [](ttl)
|
140
|
+
@results[ttl][0][0]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Invokes the given block for each ttl and result array, as described in {#[]}
|
144
|
+
def each
|
145
|
+
@results.each { |ttl,res| yield ttl, res }
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [nil,lambda] nameify optional lambda to nameify (reverse
|
149
|
+
# dns lookup or other process) an IP address when printing
|
150
|
+
# results.
|
151
|
+
# @return [String]
|
152
|
+
def to_s(nameify=nil)
|
153
|
+
nameify = lambda { |x| x } if (nameify == nil)
|
154
|
+
@results.sort.map { |ttl,res|
|
155
|
+
lastip = '';
|
156
|
+
ttl.to_s + " " + res.map { |ip,rtt,err|
|
157
|
+
if(ip != lastip) then
|
158
|
+
lastip = ip
|
159
|
+
if ip != '' then
|
160
|
+
nameify.call(ip).to_s + " "
|
161
|
+
else
|
162
|
+
""
|
163
|
+
end
|
164
|
+
else
|
165
|
+
""
|
166
|
+
end + if(rtt == '*') then
|
167
|
+
'*'
|
168
|
+
else
|
169
|
+
"%5.3f ms" % (rtt * 1000.0)
|
170
|
+
end + if(err != '') then
|
171
|
+
" " + err
|
172
|
+
else
|
173
|
+
""
|
174
|
+
end
|
175
|
+
}.join(' ')
|
176
|
+
}.join("\n")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
@@ -0,0 +1,730 @@
|
|
1
|
+
module Scriptroute
|
2
|
+
module Tulip
|
3
|
+
|
4
|
+
############# whether a given IP speaks a particular language ##########
|
5
|
+
module LangChecker;
|
6
|
+
|
7
|
+
def LangChecker.getBestOption(destination, *prefList)
|
8
|
+
prefList.each { |type|
|
9
|
+
case type
|
10
|
+
when "tstamp"
|
11
|
+
return "tstamp" if(LangChecker.probeResponse(destination,
|
12
|
+
Scriptroute::ICMP::ICMP_TSTAMP,
|
13
|
+
Scriptroute::ICMP::ICMP_TSTAMPREPLY))
|
14
|
+
when "echo"
|
15
|
+
return "echo" if(LangChecker.probeResponse(destination,
|
16
|
+
Scriptroute::ICMP::ICMP_ECHO,
|
17
|
+
Scriptroute::ICMP::ICMP_ECHOREPLY))
|
18
|
+
when "udp"
|
19
|
+
return "udp" if(LangChecker.udp(destination))
|
20
|
+
when "tcp"
|
21
|
+
return "tcp" if(LangChecker.tcp(destination))
|
22
|
+
else
|
23
|
+
raise("ERROR: unknown packet type = #{type}");
|
24
|
+
end
|
25
|
+
}
|
26
|
+
return nil;
|
27
|
+
end
|
28
|
+
|
29
|
+
def LangChecker.getBestOption4IPIDs(destination, *prefList)
|
30
|
+
prefList.each { |type|
|
31
|
+
return [type, true] if(LangChecker.increasingIPIDs(destination, 255, type));
|
32
|
+
}
|
33
|
+
return [nil, false];
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def LangChecker.probeResponse(destination, probeType, responseType)
|
38
|
+
probe = Scriptroute::ICMP.new(0);
|
39
|
+
probe.ip_dst = destination;
|
40
|
+
probe.icmp_type = probeType;
|
41
|
+
probe.icmp_code = 0;
|
42
|
+
probe.icmp_seq = 0;
|
43
|
+
packets = Scriptroute::send_train([Struct::DelayedPacket.new(0, probe)]);
|
44
|
+
return false if (!packets[0].response);
|
45
|
+
response = packets[0].response.packet;
|
46
|
+
if (response.is_a?(Scriptroute::ICMP) &&
|
47
|
+
response.icmp_type == responseType)
|
48
|
+
return true;
|
49
|
+
end
|
50
|
+
return false;
|
51
|
+
end
|
52
|
+
|
53
|
+
def LangChecker.udp(destination)
|
54
|
+
probe = Scriptroute::UDP.new(12)
|
55
|
+
probe.ip_dst = destination;
|
56
|
+
begin
|
57
|
+
packets = Scriptroute::send_train([Struct::DelayedPacket.new(0, probe)]);
|
58
|
+
rescue Exception => e
|
59
|
+
puts "sluffing exception" + e
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
return false if (!packets[0].response);
|
63
|
+
response = packets[0].response.packet;
|
64
|
+
if (response.is_a?(Scriptroute::ICMP) &&
|
65
|
+
response.icmp_type == Scriptroute::ICMP::ICMP_UNREACH &&
|
66
|
+
response.icmp_code == Scriptroute::ICMP::ICMP_UNREACH_PORT )
|
67
|
+
return true;
|
68
|
+
end
|
69
|
+
return false;
|
70
|
+
end
|
71
|
+
|
72
|
+
def LangChecker.tcp(destination)
|
73
|
+
probe = Scriptroute::TCP.new(12)
|
74
|
+
probe.ip_dst = destination;
|
75
|
+
packets = Scriptroute::send_train([Struct::DelayedPacket.new(0, probe)]);
|
76
|
+
return false if (!packets[0].response);
|
77
|
+
response = packets[0].response.packet;
|
78
|
+
puts response
|
79
|
+
return (response.is_a?(Scriptroute::TCP) && response.flag_rst)
|
80
|
+
end
|
81
|
+
|
82
|
+
def LangChecker.increasingIPIDs(destination, ttl, type)
|
83
|
+
#puts "lchecker: increasing ip-ids #{destination} #{ttl} #{type}";
|
84
|
+
delayedArray = Array.new();
|
85
|
+
(0..2).each { |i|
|
86
|
+
probe = LangChecker.getDefaultPacket(type, i);
|
87
|
+
probe.ip_dst = destination;
|
88
|
+
probe.ip_ttl = ttl;
|
89
|
+
delayedArray.push(Struct::DelayedPacket.new(0.001, probe));
|
90
|
+
}
|
91
|
+
begin
|
92
|
+
packets = Scriptroute::send_train(delayedArray);
|
93
|
+
rescue Exception => e
|
94
|
+
$stderr.puts "Exception #{e} sending:"
|
95
|
+
delayedArray.each_with_index { |dp,i| $stderr.puts "%d: %s" % [ i, dp.packet.to_s ] }
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
|
99
|
+
if (!packets[0] || !packets[1] || !packets[2] ||
|
100
|
+
!packets[0].response || !packets[1].response || !packets[2].response)
|
101
|
+
return false;
|
102
|
+
end
|
103
|
+
|
104
|
+
# puts "%s (ttl=%d, %s): %s %s %s --> %d %d %d" % [destination, ttl, type,
|
105
|
+
# packets[0].response.packet.ip_src,
|
106
|
+
# packets[1].response.packet.ip_src,
|
107
|
+
# packets[2].response.packet.ip_src,
|
108
|
+
# packets[0].response.packet.ip_id,
|
109
|
+
# packets[1].response.packet.ip_id,
|
110
|
+
# packets[2].response.packet.ip_id];
|
111
|
+
|
112
|
+
# puts "%.3f %.3f %.3f" % [packets[0].probe.time.to_f*1000,
|
113
|
+
# packets[1].probe.time.to_f*1000,
|
114
|
+
# packets[2].probe.time.to_f*1000];
|
115
|
+
|
116
|
+
|
117
|
+
##zeroes are being returned
|
118
|
+
return false if (packets[0].response.packet.ip_id == 0 && packets[1].response.packet.ip_id == 0);
|
119
|
+
|
120
|
+
##the remote destination is copying my id's
|
121
|
+
return false if (packets[0].response.packet.ip_id == packets[0].probe.packet.ip_id &&
|
122
|
+
packets[1].response.packet.ip_id == packets[1].probe.packet.ip_id);
|
123
|
+
|
124
|
+
#puts "passed zero and copy test\n";
|
125
|
+
|
126
|
+
# packets.sort! { |a,b| a.probe.time <=> b.probe.time };
|
127
|
+
ids = packets.map { |p| p.response.packet.ip_id };
|
128
|
+
|
129
|
+
return true if (AliasResolution.before(ids[0]-10, ids[1]) &&
|
130
|
+
AliasResolution.before(ids[1], ids[0]+200) &&
|
131
|
+
ids[0] != ids[1] &&
|
132
|
+
AliasResolution.before(ids[1]-10, ids[2]) &&
|
133
|
+
AliasResolution.before(ids[2], ids[1]+200) &&
|
134
|
+
ids[1] != ids[2]);
|
135
|
+
|
136
|
+
#puts "failed maternity test with #{ids.join(" ")}\n";
|
137
|
+
|
138
|
+
return false;
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def LangChecker.getDefaultPacket(type, seq=0)
|
143
|
+
if (type == "udp")
|
144
|
+
return Scriptroute::UDP.new(12);
|
145
|
+
elsif (type == "tcp")
|
146
|
+
probe = Scriptroute::TCP.new(0);
|
147
|
+
probe.th_dport=80;
|
148
|
+
probe.th_win=1024;
|
149
|
+
return probe;
|
150
|
+
else
|
151
|
+
probe = if (type == "echo")
|
152
|
+
Scriptroute::ICMPecho.new(0)
|
153
|
+
elsif (type == "tstamp")
|
154
|
+
Scriptroute::ICMPtstamp.new(0)
|
155
|
+
else
|
156
|
+
raise "ERROR: unsupported packettype #{type} in sendAndReceiveTrain\n";
|
157
|
+
end
|
158
|
+
probe.icmp_seq=seq;
|
159
|
+
return probe;
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def LangChecker.getBestPathOption4IPIDs(destination, ttl, router, tpath)
|
164
|
+
canCheckForward = (LangChecker.increasingIPIDs(destination, ttl, "udp"))? 1 : -1;
|
165
|
+
#puts "ccf = #{canCheckForward}\n";
|
166
|
+
if (canCheckForward == -1) then
|
167
|
+
##todo: can be extended to other types, such as timestamps
|
168
|
+
if (LangChecker.increasingIPIDs(router, 255, "echo")) then
|
169
|
+
subpath = TracePath.new(router);
|
170
|
+
puts "subpathto #{router}:\n#{subpath.to_s}\n";
|
171
|
+
if (tpath.subset(subpath)) then
|
172
|
+
return [router, 255, "echo", 1];
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
## the default
|
177
|
+
return [destination, ttl, "udp", canCheckForward];
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
end ##module LangChecker
|
183
|
+
|
184
|
+
|
185
|
+
############### alias resolution: ally + ttl-testing + result cache #######
|
186
|
+
class AliasResolution
|
187
|
+
@@aliasCache = Hash.new(-1);
|
188
|
+
@@logAllyResults = false;
|
189
|
+
@@doTTLTesting = false;
|
190
|
+
@@seqIPIDCache = Hash.new(-1);
|
191
|
+
|
192
|
+
@@ally = (FileTest.executable?("/usr/bin/sr-ally"))? "/usr/bin/sr-ally" : "/usr/local/bin/sr-ally";
|
193
|
+
|
194
|
+
def AliasResolution.cacheKey (ip1, ip2)
|
195
|
+
(ip1 < ip2)? "#{ip1}:#{ip2}" : "#{ip2}:#{ip1}";
|
196
|
+
end
|
197
|
+
|
198
|
+
def AliasResolution.aliases (hop1, hop2)
|
199
|
+
key = cacheKey(hop1.ip, hop2.ip);
|
200
|
+
return @@aliasCache[key] if (@@aliasCache[key] != -1)
|
201
|
+
|
202
|
+
output = `#{@@ally} #{hop1.ip} #{hop2.ip}`
|
203
|
+
output.gsub!(/\n/, " ");
|
204
|
+
Kernel::system("echo '#{hop1.ip} #{hop2.ip} #{output}' >> ally.stats") if (@@logAllyResults);
|
205
|
+
|
206
|
+
if (/ALIAS/.match(output) && !/NOT/.match(output)) then
|
207
|
+
@@aliasCache[key] = true;
|
208
|
+
return true;
|
209
|
+
elsif (@@doTTLTesting and /UNKNOWN/.match(output)) then
|
210
|
+
ttlSays = ttlBasedTest(hop1, hop2);
|
211
|
+
Kernel::system("echo 'ttlTest: #{hop1} #{hop2} #{ttlSays}' >> ally.stats") if (@@logAllyResults);
|
212
|
+
return ttlSays[0];
|
213
|
+
else
|
214
|
+
@@aliasCache[key] = false;
|
215
|
+
return false;
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def AliasResolution.seqIPID (thop)
|
220
|
+
if (@@seqIPIDCache["#{thop.dst} #{thop.hop}"] == -1)
|
221
|
+
@@seqIPIDCache["#{thop.dst} #{thop.hop}"] = LangChecker.increasingIPIDs(thop.dst, thop.hop, "udp");
|
222
|
+
puts "seqIPID results for #{thop.dst} #{thop.hop} is %s\n" % [@@seqIPIDCache["#{thop.dst} #{thop.hop}"]];
|
223
|
+
end
|
224
|
+
|
225
|
+
return @@seqIPIDCache["#{thop.dst} #{thop.hop}"];
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
def AliasResolution.ttlBasedTest (hop1, hop2)
|
230
|
+
return [false, "non-seq-ipid"] if (!seqIPID(hop1) or !seqIPID(hop2));
|
231
|
+
|
232
|
+
a = Scriptroute::UDP.new(12)
|
233
|
+
b = Scriptroute::UDP.new(12)
|
234
|
+
a.ip_dst, a.ip_ttl = hop1.dst, hop1.hop;
|
235
|
+
b.ip_dst, b.ip_ttl = hop2.dst, hop2.hop;
|
236
|
+
|
237
|
+
packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,a),
|
238
|
+
Struct::DelayedPacket.new(0.001,b) ])
|
239
|
+
|
240
|
+
return [false, "insufficient info"] if (!packets[0] || !packets[1] ||
|
241
|
+
!packets[0].response || !packets[1].response ||
|
242
|
+
packets[0].response.packet.ip_src != hop1.ip ||
|
243
|
+
packets[1].response.packet.ip_src != hop2.ip);
|
244
|
+
|
245
|
+
id0 = packets[0].response.packet.ip_id
|
246
|
+
id1 = packets[1].response.packet.ip_id
|
247
|
+
return [false, "ids dont match"] if (!before(id0-10, id1) || !before(id1, id0+200) || id0 == id1);
|
248
|
+
|
249
|
+
packetz = Scriptroute::send_train([ Struct::DelayedPacket.new(0,b),
|
250
|
+
Struct::DelayedPacket.new(0.001,a) ])
|
251
|
+
|
252
|
+
return [false, "insufficient info2"] if (!packetz[0] || !packetz[1] ||
|
253
|
+
!packetz[0].response || !packetz[1].response ||
|
254
|
+
packetz[0].response.packet.ip_src != hop2.ip ||
|
255
|
+
packetz[1].response.packet.ip_src != hop1.ip);
|
256
|
+
|
257
|
+
id2 = packets[0].response.packet.ip_id
|
258
|
+
id3 = packets[1].response.packet.ip_id
|
259
|
+
return [true, "excellent"] if(before(id2-10, id3) && before(id3, id2+200) && id3 != id2 &&
|
260
|
+
before(id0, id2) && before(id1, id3) &&
|
261
|
+
id2 != id1 && id2 != id0 && id3 != id1 && id3 != id0 );
|
262
|
+
|
263
|
+
return [false, "final failure"];
|
264
|
+
end
|
265
|
+
|
266
|
+
def AliasResolution.before(seq1,seq2)
|
267
|
+
diff = seq1-seq2
|
268
|
+
# emulate signed short arithmetic.
|
269
|
+
if (diff > 32767) then
|
270
|
+
diff-=65535;
|
271
|
+
elsif (diff < -32768) then
|
272
|
+
diff+=65535;
|
273
|
+
end
|
274
|
+
# puts "#{seq1} #{diff < 0 ? "" : "not"} before #{seq2}\n"
|
275
|
+
(diff < 0)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
|
281
|
+
############# generic train ################
|
282
|
+
class Train
|
283
|
+
attr_reader :dsts, :numpackets, :resolution, :types, :ttls, :reverse_ttls, :num_losses, :packets, :shuffle;
|
284
|
+
|
285
|
+
def getType (pkt)
|
286
|
+
if (pkt.is_a?(Scriptroute::TCP))
|
287
|
+
return "tcp";
|
288
|
+
elsif (pkt.is_a?(Scriptroute::UDP))
|
289
|
+
return "udp";
|
290
|
+
elsif (pkt.is_a?(Scriptroute::ICMP))
|
291
|
+
if (pkt.icmp_type == Scriptroute::ICMP::ICMP_ECHO)
|
292
|
+
return "echo";
|
293
|
+
elsif (pkt.icmp_type == Scriptroute::ICMP::ICMP_TSTAMP)
|
294
|
+
return "tstamp";
|
295
|
+
end
|
296
|
+
end
|
297
|
+
raise "ERROR: unknown packet type: #{pkt.to_s}";
|
298
|
+
end
|
299
|
+
|
300
|
+
def getIndex (pr)
|
301
|
+
pkt = pr.probe.packet;
|
302
|
+
@dsts.each_index { |i|
|
303
|
+
if (pkt.ip_dst == @dsts[i] and
|
304
|
+
pkt.ip_ttl == @ttls[i] and
|
305
|
+
getType(pkt) == @types[i])
|
306
|
+
return i;
|
307
|
+
end
|
308
|
+
}
|
309
|
+
raise "ERROR: could not classify packet: #{pkt.to_s}";
|
310
|
+
end
|
311
|
+
|
312
|
+
def initialize(dsts, numpackets, resolution, types, ttls, shuffle=false)
|
313
|
+
@dsts, @numpackets, @resolution, @types, @ttls, @shuffle = dsts, numpackets, resolution, types, ttls, shuffle;
|
314
|
+
@reverse_ttls, @rem_srcs, @num_losses, @packets = Array.new(), Array.new(), Array.new(), Array.new();
|
315
|
+
(@dsts).each_index { |i|
|
316
|
+
@packets[i], @reverse_ttls[i], @rem_srcs[i], @num_losses[i] = Array.new(), Array.new(), Array.new(), 0;
|
317
|
+
}
|
318
|
+
|
319
|
+
num_dsts = @dsts.length;
|
320
|
+
max_train = (MAX_TRAIN/num_dsts).floor;
|
321
|
+
seq_id = 0; order = 0;
|
322
|
+
allResponsePackets = Array.new();
|
323
|
+
( 0 .. (numpackets/max_train).ceil ).each { |trainnum|
|
324
|
+
packetsThisRound = (numpackets < (trainnum+1)*max_train)? (numpackets - trainnum*max_train) : max_train;
|
325
|
+
delayedPackets = Array.new();
|
326
|
+
( seq_id .. seq_id + packetsThisRound-1).each { |rep|
|
327
|
+
|
328
|
+
##construct probe packets
|
329
|
+
probes = Array.new();
|
330
|
+
(@dsts).each_index { |i|
|
331
|
+
case types[i]
|
332
|
+
when "tcp"
|
333
|
+
probes[i] = Scriptroute::TCP.new(12);
|
334
|
+
when "udp"
|
335
|
+
probes[i] = Scriptroute::UDP.new(12);
|
336
|
+
probes[i].uh_dport = 33444 ## unknown effect; five-tuple balancing may occur
|
337
|
+
when "echo"
|
338
|
+
probes[i] = Scriptroute::ICMP.new(0)
|
339
|
+
probes[i].icmp_type = Scriptroute::ICMP::ICMP_ECHO;
|
340
|
+
probes[i].icmp_code = 0;
|
341
|
+
probes[i].icmp_seq = rep;
|
342
|
+
when "tstamp"
|
343
|
+
probes[i] = Scriptroute::ICMP.new(0)
|
344
|
+
probes[i].icmp_type = Scriptroute::ICMP::ICMP_TSTAMP;
|
345
|
+
probes[i].icmp_code = 0;
|
346
|
+
probes[i].icmp_seq = rep;
|
347
|
+
else
|
348
|
+
raise("ERROR: unsupported packettype #{types[i]} in sendAndReceiveTrain\n");
|
349
|
+
end
|
350
|
+
probes[i].ip_ttl = @ttls[i];
|
351
|
+
probes[i].ip_dst = @dsts[i];
|
352
|
+
@dsts[i] = probes[i].ip_dst; # accomplishes a name lookup so responses can be classified - nspring
|
353
|
+
|
354
|
+
}
|
355
|
+
|
356
|
+
##insert probe packets in the order desired
|
357
|
+
(@dsts).each_index { |i|
|
358
|
+
delay = (i==0)? resolution : 0;
|
359
|
+
#puts "delay: #{delay} (#{i})";
|
360
|
+
insert = (order + i) % num_dsts;
|
361
|
+
delayedPackets.push(Struct::DelayedPacket.new(delay, probes[insert]));
|
362
|
+
order = (order + 1) % num_dsts if (@shuffle);
|
363
|
+
}
|
364
|
+
} ##each rep
|
365
|
+
|
366
|
+
#puts "before real = %.3f" % [Time.now.to_f*1000];
|
367
|
+
allResponsePackets.push(Scriptroute::send_train(delayedPackets));
|
368
|
+
#puts "after real = %.3f" % [Time.now.to_f*1000];
|
369
|
+
|
370
|
+
} ##each trainnum
|
371
|
+
allResponsePackets.flatten!
|
372
|
+
#allResponsePackets.sort!{ |a,b| a.probe.time <=> b.probe.time }
|
373
|
+
|
374
|
+
allResponsePackets.each { |pr|
|
375
|
+
if (pr and pr.probe and pr.probe.time)
|
376
|
+
i = getIndex(pr);
|
377
|
+
@packets[i].push(pr);
|
378
|
+
if (pr.response) then
|
379
|
+
@reverse_ttls[i].push(pr.response.packet.ip_ttl) if !@reverse_ttls[i].index(pr.response.packet.ip_ttl);
|
380
|
+
@rem_srcs[i].push(pr.response.packet.ip_src) if !@rem_srcs[i].index(pr.response.packet.ip_src);
|
381
|
+
else
|
382
|
+
@num_losses[i] += 1;
|
383
|
+
end
|
384
|
+
end
|
385
|
+
}
|
386
|
+
|
387
|
+
# nspring thinks the following is a no-op.
|
388
|
+
(@dsts).each_index { |i|
|
389
|
+
@packets[i].sort! { |a,b| a.probe.time <=> b.probe.time }
|
390
|
+
}
|
391
|
+
|
392
|
+
##check sanity of the train
|
393
|
+
(@dsts).each_index { |i|
|
394
|
+
if (@reverse_ttls[i].length > 1)
|
395
|
+
puts "WARNING: too many reverse TTLs for index #{i}: #{@reverse_ttls[i].join(" ")}";
|
396
|
+
end
|
397
|
+
|
398
|
+
if (@rem_srcs[i].length > 1)
|
399
|
+
puts "WARNING: too many remote sources for index #{i}: #{@rem_srcs[i].join(" ")}";
|
400
|
+
end
|
401
|
+
|
402
|
+
##todo: should really check a pattern in losses
|
403
|
+
## to distinguish between high loss rate and rate-limiting
|
404
|
+
if (@num_losses[i] > 0.2*@numpackets)
|
405
|
+
#STDERR.puts "WARNING: too many losses #{@num_losses[i]} for index #{i}";
|
406
|
+
end
|
407
|
+
}
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
def to_s
|
412
|
+
str = "train: #{@dsts} #{@numpackets} #{@resolution} (#{@types}) (#{@ttls})\n";
|
413
|
+
@packets[0].each_index { |i|
|
414
|
+
(@dsts).each_index { |j|
|
415
|
+
pr = @packets[j][i];
|
416
|
+
stime = pr.probe.time.to_f * 1000;
|
417
|
+
srcid = pr.probe.packet.ip_id;
|
418
|
+
rtt = (pr.probe and pr.response) ? (pr.response.time - pr.probe.time) * 1000 : -1;
|
419
|
+
str += "#{@dsts[j]} %d %.3f %d " %[rtt, stime, srcid];
|
420
|
+
if (@types[j] == "tstamp") then
|
421
|
+
rem_time = (pr.response)? pr.response.packet.icmp_ttime : 0;
|
422
|
+
ott = (pr.response)? stime - pr.response.packet.icmp_ttime : -1;
|
423
|
+
str += "#{rem_time} %.3f " % [ott];
|
424
|
+
end
|
425
|
+
}
|
426
|
+
str += "\n";
|
427
|
+
}
|
428
|
+
str += "losses: #{@num_losses.join(" ")}\n";
|
429
|
+
return str;
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
434
|
+
################ train2 ##############
|
435
|
+
class Train2
|
436
|
+
attr_reader :dsts, :numpackets, :intratrain, :types, :ttls, :psizes, :numtrains, :intertrain, :reverse_ttls, :num_losses, :packets;
|
437
|
+
|
438
|
+
def getProbe (i,j)
|
439
|
+
dst = (@dsts[j])? @dsts[j] : @dsts[0];
|
440
|
+
ttl = (@ttls[j])? @ttls[j] : @ttls[0];
|
441
|
+
type = (@types[j])? @types[j] : @types[0];
|
442
|
+
psize = (@psizes[j])? @psizes[j] : @psizes[0];
|
443
|
+
|
444
|
+
probe = nil;
|
445
|
+
case type
|
446
|
+
when "udp"
|
447
|
+
psize = (psize==-1)? 12 : psize;
|
448
|
+
probe = Scriptroute::UDP.new(psize);
|
449
|
+
probe.uh_dport = 33444;
|
450
|
+
when "tcp"
|
451
|
+
psize = (psize==-1)? 0 : psize;
|
452
|
+
probe = Scriptroute::TCP.new(psize);
|
453
|
+
probe.th_dport=80;
|
454
|
+
probe.th_win=1024;
|
455
|
+
else # "echo" or "tstamp"
|
456
|
+
psize = (psize==-1)? 0 : psize;
|
457
|
+
probe = Scriptroute::ICMP.new(psize);
|
458
|
+
if (type == "echo")
|
459
|
+
probe.icmp_type = Scriptroute::ICMP::ICMP_ECHO;
|
460
|
+
elsif (type == "tstamp")
|
461
|
+
probe.icmp_type = Scriptroute::ICMP::ICMP_TSTAMP;
|
462
|
+
else
|
463
|
+
raise "ERROR: unsupported packettype #{i} #{j} #{type}\n";
|
464
|
+
end
|
465
|
+
probe.icmp_code = 0;
|
466
|
+
probe.icmp_seq = i*numpackets + j;
|
467
|
+
end
|
468
|
+
probe.ip_ttl = ttl;
|
469
|
+
probe.ip_dst = dst;
|
470
|
+
|
471
|
+
return probe;
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
def initialize(dsts, numpackets, intratrain, types, ttls, psizes, numtrains, intertrain)
|
476
|
+
@dsts, @numpackets, @intratrain, @types, @ttls, @psizes, @numtrains, @intertrain =
|
477
|
+
dsts, numpackets, intratrain, types, ttls, psizes, numtrains, intertrain;
|
478
|
+
@reverse_ttls, @rem_srcs, @num_losses, @packets = Hash.new(), Hash.new(), Array.new(), Array.new();
|
479
|
+
|
480
|
+
##forget the MAX_TRAIN complication for this part;
|
481
|
+
|
482
|
+
delayedPackets = Array.new();
|
483
|
+
(0..@numtrains-1).each { |i|
|
484
|
+
delay = (i==0)? 0 : intertrain;
|
485
|
+
(0..@numpackets-1).each { |j|
|
486
|
+
probe = getProbe(i,j);
|
487
|
+
#puts "#{i} #{delay}\n";
|
488
|
+
delayedPackets.push(Struct::DelayedPacket.new(delay, probe));
|
489
|
+
delay = intratrain;
|
490
|
+
delay = (j==@numpackets-2)? 0.003 : 0.0005;
|
491
|
+
}
|
492
|
+
}
|
493
|
+
@responsesPackets = Scriptroute::send_train(delayedPackets);
|
494
|
+
|
495
|
+
##check sanity of the train and insert it into @packets and @num_losses
|
496
|
+
(0..@numtrains-1).each { |i|
|
497
|
+
@packets[i] = Array.new();
|
498
|
+
@num_losses[i] = 0;
|
499
|
+
(0..@numpackets-1).each { |j|
|
500
|
+
index = i*@numpackets + j;
|
501
|
+
pkt = @responsesPackets[index];
|
502
|
+
@packets[i].push(pkt);
|
503
|
+
|
504
|
+
dst = (@dsts[j])? @dsts[j] : @dsts[0];
|
505
|
+
ttl = (@ttls[j])? @ttls[j] : @ttls[0];
|
506
|
+
key = "#{dst}::#{ttl}";
|
507
|
+
if (!@reverse_ttls[key])
|
508
|
+
@reverse_ttls[key] = Hash.new(0);
|
509
|
+
@rem_srcs[key] = Hash.new(0);
|
510
|
+
end
|
511
|
+
|
512
|
+
if (pkt)
|
513
|
+
if (pkt.probe and pkt.probe.time)
|
514
|
+
if (pkt.response)
|
515
|
+
rttl = pkt.response.packet.ip_ttl;
|
516
|
+
src = pkt.response.packet.ip_src;
|
517
|
+
@reverse_ttls[key][rttl] += 1;
|
518
|
+
@rem_srcs[key][src] += 1;
|
519
|
+
else
|
520
|
+
@num_losses[i]+=1;
|
521
|
+
end
|
522
|
+
##check the probe sort order
|
523
|
+
npkt = @responsesPackets[index+1];
|
524
|
+
if (npkt and npkt.probe and npkt.probe.time and pkt.probe.time > npkt.probe.time)
|
525
|
+
STDERR.puts "ERROR: packets not in probe sorted order. \n%s %d %.3f\n%s %d %.3f\n" %
|
526
|
+
[pkt.probe.packet.ip_dst, pkt.probe.packet.ip_ttl, pkt.probe.time.to_f*1000,
|
527
|
+
npkt.probe.packet.ip_dst, npkt.probe.packet.ip_ttl, npkt.probe.time.to_f*1000];
|
528
|
+
end
|
529
|
+
end
|
530
|
+
else
|
531
|
+
raise "ERROR: there are null responses\n";
|
532
|
+
end
|
533
|
+
}
|
534
|
+
}
|
535
|
+
|
536
|
+
@reverse_ttls.each_key { |key|
|
537
|
+
if (@reverse_ttls[key].keys.length > 1)
|
538
|
+
puts "WARNING: too many reverse TTLs for #{key}. #{@reverse_ttls[key].keys.join(" ")} #{@reverse_ttls[key].values.join(" ")} ";
|
539
|
+
end
|
540
|
+
|
541
|
+
if (@rem_srcs[key].keys.length > 1)
|
542
|
+
puts "WARNING: too many sources. #{@rem_srcs[key].keys.join(" ")} #{@rem_srcs[key].values.join(" ")} ";
|
543
|
+
end
|
544
|
+
}
|
545
|
+
|
546
|
+
end
|
547
|
+
|
548
|
+
def to_s
|
549
|
+
str = "train: #{@dsts} #{@numpackets} #{@intratrain} (#{@types}) (#{@ttls})\n";
|
550
|
+
(0..@numtrains-1).each { |i|
|
551
|
+
str += "train #{i}\n";
|
552
|
+
(0..@numpackets-1).each { |j|
|
553
|
+
pr = @packets[i][j];
|
554
|
+
src = (pr.response)? pr.response.packet.ip_src : -1;
|
555
|
+
srcid = pr.probe.packet.ip_id;
|
556
|
+
stime = pr.probe.time.to_f * 1000;
|
557
|
+
rtt = (pr.probe and pr.response) ? (pr.response.time - pr.probe.time) * 1000 : -1;
|
558
|
+
ipid = (pr.response)? pr.response.packet.ip_id : -1;
|
559
|
+
str += "#{src} %d %.3f %d " %[rtt, stime, ipid];
|
560
|
+
if (@types[j] == "tstamp") then
|
561
|
+
rem_time = (pr.response)? pr.response.packet.icmp_ttime : 0;
|
562
|
+
str += "#{rem_time} ";
|
563
|
+
end
|
564
|
+
}
|
565
|
+
str += "\n";
|
566
|
+
}
|
567
|
+
return str;
|
568
|
+
end
|
569
|
+
|
570
|
+
def simple_to_s
|
571
|
+
str = "";
|
572
|
+
(0..@numtrains-1).each { |i|
|
573
|
+
(0..@numpackets-1).each { |j|
|
574
|
+
pr = @packets[i][j];
|
575
|
+
src = (pr.response)? pr.response.packet.ip_src : -1;
|
576
|
+
stime = pr.probe.time.to_f * 1000;
|
577
|
+
ipid = (pr.response)? pr.response.packet.ip_id : -1;
|
578
|
+
str += "%.3f %d " %[stime, ipid];
|
579
|
+
}
|
580
|
+
str += "\n";
|
581
|
+
}
|
582
|
+
return str;
|
583
|
+
end
|
584
|
+
|
585
|
+
end
|
586
|
+
|
587
|
+
|
588
|
+
################## a traceroute hop ##################
|
589
|
+
class TraceHop
|
590
|
+
@@aliasTesting = false;
|
591
|
+
|
592
|
+
attr_reader :hop, :ip, :dst, :name;
|
593
|
+
def initialize (hop, ip, dst, name=ip)
|
594
|
+
@hop = hop;
|
595
|
+
@ip = ip;
|
596
|
+
@dst = dst;
|
597
|
+
@name = name;
|
598
|
+
end
|
599
|
+
|
600
|
+
##todo: set this right
|
601
|
+
def == (another)
|
602
|
+
return true if (@ip == another.ip);
|
603
|
+
return AliasResolution.aliases(self, another) if (@@aliasTesting);
|
604
|
+
return false;
|
605
|
+
end
|
606
|
+
|
607
|
+
def to_s
|
608
|
+
return "#{@hop.to_s} #{@name.to_s} (#{@ip.to_s})";
|
609
|
+
end
|
610
|
+
|
611
|
+
end
|
612
|
+
|
613
|
+
#################### the traceroute path ############
|
614
|
+
|
615
|
+
class TracePath
|
616
|
+
attr_reader :path, :dst, :speaks_udp, :status_code;
|
617
|
+
|
618
|
+
def initialize (dst, resolveNames=false, start_ttl=1, end_ttl=30)
|
619
|
+
@path = Array.new;
|
620
|
+
@dst = dst;
|
621
|
+
@speaks_udp = (LangChecker.udp(@dst)) ? true : false;
|
622
|
+
@status_code = "incomplete";
|
623
|
+
|
624
|
+
#make self as first hop; simplifies binary search.
|
625
|
+
@path.push(TraceHop.new(0, "0", dst));
|
626
|
+
|
627
|
+
probe = Scriptroute::UDP.new(12)
|
628
|
+
probe.ip_dst = dst
|
629
|
+
port_unreach = false
|
630
|
+
|
631
|
+
catch :port_unreachable do
|
632
|
+
(start_ttl..end_ttl).each { |ttl|
|
633
|
+
probe.ip_ttl = ttl
|
634
|
+
#puts "before trace: %.3f" % [Time.now.to_f];
|
635
|
+
packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0, probe) ]);
|
636
|
+
#puts "after trace: %.3f" % [Time.now.to_f];
|
637
|
+
if (!packets[0]) then
|
638
|
+
STDERR.puts("WARNING: scriptrouted err'd. sleeping and retrying");
|
639
|
+
Kernel::sleep(5);
|
640
|
+
redo;
|
641
|
+
end;
|
642
|
+
response = (packets[0].response) ? packets[0].response.packet : nil
|
643
|
+
if (response.is_a?(Scriptroute::ICMP)) then
|
644
|
+
name = response.ip_src;
|
645
|
+
if (resolveNames) then
|
646
|
+
begin
|
647
|
+
name = Socket.gethostbyname(response.ip_src)[0];
|
648
|
+
rescue
|
649
|
+
end
|
650
|
+
end
|
651
|
+
@path.push(TraceHop.new(ttl, response.ip_src, dst, name)) if (!containsIP(response.ip_src));
|
652
|
+
port_unreach = true if (response.icmp_type == 3)
|
653
|
+
@status_code = "complete" if (response.icmp_type == 3 && response.icmp_code == 3)
|
654
|
+
else
|
655
|
+
port_unreach = true if (ttl - @path.last.hop >=2 and !@speaks_udp);
|
656
|
+
end
|
657
|
+
throw :port_unreachable if(port_unreach)
|
658
|
+
}
|
659
|
+
end
|
660
|
+
|
661
|
+
end
|
662
|
+
|
663
|
+
def containsIP(ip)
|
664
|
+
@path[1..-1].map { |p| return true if (p.ip == ip) };
|
665
|
+
return false;
|
666
|
+
end
|
667
|
+
|
668
|
+
def getClosestTSNode(starthop, delta, minhop, maxhop)
|
669
|
+
while (starthop >= minhop && starthop <= maxhop)
|
670
|
+
if LangChecker.probeResponse(@path[starthop].ip,
|
671
|
+
Scriptroute::ICMP::ICMP_TSTAMP,
|
672
|
+
Scriptroute::ICMP::ICMP_TSTAMPREPLY) then
|
673
|
+
return starthop;
|
674
|
+
end
|
675
|
+
starthop += delta;
|
676
|
+
end
|
677
|
+
return nil;
|
678
|
+
end
|
679
|
+
|
680
|
+
def subset (another, minhop = 1)
|
681
|
+
lasthop = another.path.last.hop;
|
682
|
+
matches = Array.new();
|
683
|
+
mismatches = Array.new();
|
684
|
+
empties = Array.new();
|
685
|
+
(minhop..lasthop).each { |hop|
|
686
|
+
myhop = @path[hop];
|
687
|
+
hishop = another.path[hop];
|
688
|
+
if (myhop and hishop)
|
689
|
+
(myhop == hishop)? matches.push(hop) : mismatches.push(hop);
|
690
|
+
elsif (myhop or hishop)
|
691
|
+
empties.push(hop);
|
692
|
+
end
|
693
|
+
}
|
694
|
+
lastMatch = true;
|
695
|
+
if (another.status_code == "incomplete")
|
696
|
+
index = getIndex(another.dst);
|
697
|
+
if (index < 0 or lasthop + 1 < index) then
|
698
|
+
lastMatch = false;
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
#puts "subset results. matches: #{matches.join(" ")} mismatches: #{mismatches.join(" ")} empties: #{empties.join(" ")} lastMatch: #{lastMatch}";
|
703
|
+
|
704
|
+
return true if ((mismatches.length == 0 or mismatches.last <= $prefixpath) && ##let go for local load balancing
|
705
|
+
lastMatch and matches.index(lasthop)); ##the last hop has to match
|
706
|
+
return false;
|
707
|
+
end
|
708
|
+
|
709
|
+
def checkValidAnchors(farindex, farpath, nearindex, nearpath, verbose=true)
|
710
|
+
return (farpath.subset(nearpath) and subset(farpath, path[nearindex].hop));
|
711
|
+
end
|
712
|
+
|
713
|
+
def getIndex(ip)
|
714
|
+
(1..@path.length-1).each { |i|
|
715
|
+
if (@path[i].ip == ip)
|
716
|
+
return i;
|
717
|
+
end
|
718
|
+
}
|
719
|
+
return -1;
|
720
|
+
end
|
721
|
+
|
722
|
+
def to_s
|
723
|
+
@path[1..-1].map { |p| p.to_s }.join("\n");
|
724
|
+
end
|
725
|
+
|
726
|
+
end
|
727
|
+
|
728
|
+
|
729
|
+
end
|
730
|
+
end
|