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