scriptroute 0.4.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3d192161acfbf508dfd7d953bc7ec1a219930d15
|
4
|
+
data.tar.gz: a3c7cbd73442f9aa4cc263830a71b62023f08355
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a98db9cec4c8d90da6c8958e68d063d3049c68d88ac612184abfef44a789f8b57120b2325625f3704c770db3a6557aa2ad6336d36655be28067426b0fff69f0
|
7
|
+
data.tar.gz: 77be1bb191576fa8890c8395c1d93577e060cfe4a846989c74c51ae24797e3dbf1c78f091dbedc33ef72c3aa78c373ba391cfe1ee1eed3dd88c70815dee82425
|
data/bin/sr-ally
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'scriptroute/ally'
|
4
|
+
require 'scriptroute/ipaddr4'
|
5
|
+
|
6
|
+
if(ARGV.length < 2) then
|
7
|
+
puts "ERROR: need two ip addresses to compare."
|
8
|
+
exit 3;
|
9
|
+
end
|
10
|
+
|
11
|
+
verdict = Ally.new(ARGV[0], ARGV[1]);
|
12
|
+
puts verdict
|
13
|
+
exit case verdict.to_s
|
14
|
+
when /^ALIAS/
|
15
|
+
0;
|
16
|
+
when /^NOT ALIAS/
|
17
|
+
1;
|
18
|
+
when /^UNKNOWN/
|
19
|
+
2;
|
20
|
+
end
|
21
|
+
|
data/bin/sr-liveness
ADDED
data/bin/sr-ping-T
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- mode: Ruby -*-
|
3
|
+
|
4
|
+
require 'scriptroute'
|
5
|
+
require 'scriptroute/packets'
|
6
|
+
require 'scriptroute/nameify'
|
7
|
+
|
8
|
+
$have_socket = begin
|
9
|
+
require 'socket'
|
10
|
+
true
|
11
|
+
rescue
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
Scriptroute::daemon_running_or_exit
|
16
|
+
|
17
|
+
i = Scriptroute::ICMPecho.new(0)
|
18
|
+
# i.ip_dst = 0x805f0218 # poplar
|
19
|
+
i.ip_dst = Scriptroute::dns_lookup(ARGV[0])
|
20
|
+
# i.ip_dst = 0xc6ca4b65 # www.sdsc.edu
|
21
|
+
# rr = RecordRoute_option.new
|
22
|
+
# IPOPT_TS_TSONLY = 0
|
23
|
+
# IPOPT_TS_TSANDADDR = 1
|
24
|
+
# IPOPT_TS_PRESPEC = 3
|
25
|
+
rr = Scriptroute::Timestamp_option.new(Scriptroute::IPv4option::IPOPT_TS_TSONLY)
|
26
|
+
i.ip_ttl=15
|
27
|
+
i.add_option(rr)
|
28
|
+
|
29
|
+
p = Scriptroute::send_train([ Struct::DelayedPacket.new(0, i) ])
|
30
|
+
|
31
|
+
# p[0].response.packet.to_bytes.each_byte { |b| puts "%x " % b }
|
32
|
+
|
33
|
+
if (p[0].response != nil) then
|
34
|
+
if ($VERBOSE) then puts p[0].response.packet end
|
35
|
+
resp = ""
|
36
|
+
begin
|
37
|
+
resp = Scriptroute::IPv4.creator(p[0].response.packet.to_bytes)
|
38
|
+
resp.ip_options.each { |opt|
|
39
|
+
if(opt.is_a?(Scriptroute::Timestamp_option)) then
|
40
|
+
if(resp.ip_options[0].routers != nil &&
|
41
|
+
resp.ip_options[0].times != nil && resp.ip_options[0].times.length > 0) then
|
42
|
+
opt.times.each_index { |i|
|
43
|
+
puts Scriptroute.nameify(resp.ip_options[0].routers[i]) + " " + resp.ip_options[0].times[i].to_s
|
44
|
+
}
|
45
|
+
else
|
46
|
+
opt.times.each_index { |i|
|
47
|
+
puts resp.ip_options[0].times[i].to_s
|
48
|
+
}
|
49
|
+
end
|
50
|
+
elsif(opt.is_a?(Scriptroute::RecordRoute_option)) then
|
51
|
+
opt.routers.each_index { |i|
|
52
|
+
puts "%d %s" % [ i+1, Scriptroute.nameify(resp.ip_options[0].routers[i]) ]
|
53
|
+
}
|
54
|
+
else
|
55
|
+
raise "unknown option class %s" % opt.class.to_s
|
56
|
+
end
|
57
|
+
}
|
58
|
+
rescue => e
|
59
|
+
puts "failed due to #{e} parsing: #{resp}"
|
60
|
+
end
|
61
|
+
else
|
62
|
+
puts "no response to ping -r from %s" % p[0].probe.packet.ip_dst
|
63
|
+
end
|
64
|
+
|
65
|
+
# .map { |a|
|
66
|
+
# a
|
67
|
+
#nameify(a)
|
68
|
+
#}
|
69
|
+
|
data/bin/sr-rockettrace
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'scriptroute'
|
4
|
+
require 'scriptroute/rockettrace'
|
5
|
+
require 'scriptroute/nameify'
|
6
|
+
require 'scriptroute/commando'
|
7
|
+
|
8
|
+
c = Commando.new(ARGV, # allows substitution by srclient.rb
|
9
|
+
[ CommandoVar.new( [ "-S", "--start-ttl" ],
|
10
|
+
"hops out to start" ,
|
11
|
+
:$StartTTL, 1),
|
12
|
+
CommandoVar.new( [ "-n", "--numeric" ],
|
13
|
+
"do no hostname lookup or interpretation" ,
|
14
|
+
:$Numeric, false),
|
15
|
+
CommandoVar.new( [ "-q", "--queries" ],
|
16
|
+
"number of probes at each ttl" ,
|
17
|
+
:$Repetitions, 3),
|
18
|
+
# todo CommandoVar.new( "--use-icmp",
|
19
|
+
# todo "use icmp probes instead of udp" ,
|
20
|
+
# todo :$UseICMP, false ) ],
|
21
|
+
CommandoVar.new( [ "-o", "--output" ],
|
22
|
+
"file to use instead of stdout" ,
|
23
|
+
:$Output, ""),
|
24
|
+
CommandoVar.new( "--use-tcp",
|
25
|
+
"use tcp probes instead of udp" ,
|
26
|
+
:$UseTCP, false ) ],
|
27
|
+
"destination-host")
|
28
|
+
|
29
|
+
raise "must start with a positive ttl" if($StartTTL <= 0)
|
30
|
+
if(ARGV[0] == nil) then
|
31
|
+
c.usage
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
Scriptroute::daemon_running_or_exit
|
36
|
+
|
37
|
+
if($Output != "") then
|
38
|
+
$stdout.reopen(File.open($Output,"a"))
|
39
|
+
end
|
40
|
+
|
41
|
+
Destination = (ARGV[0] =~ /(\d{1,3}\.){3}\d{1,3}/) ? ARGV[0] : Scriptroute.dns_lookup(ARGV[0])
|
42
|
+
puts Scriptroute::Rockettrace.new(Destination, $StartTTL, $UseTCP, $Repetitions).to_s(lambda { |x|
|
43
|
+
if($Numeric) then
|
44
|
+
x
|
45
|
+
elsif(x=='' || x==nil)
|
46
|
+
"[empty]"
|
47
|
+
else
|
48
|
+
Scriptroute.nameify(x)
|
49
|
+
end
|
50
|
+
})
|
51
|
+
|
data/bin/sr-traceroute
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'scriptroute'
|
4
|
+
|
5
|
+
Scriptroute::daemon_running_or_exit
|
6
|
+
|
7
|
+
probe = Scriptroute::UDP.new(12)
|
8
|
+
|
9
|
+
probe.ip_dst = Scriptroute::dns_lookup(ARGV[0])
|
10
|
+
|
11
|
+
unreach = false
|
12
|
+
|
13
|
+
puts "Traceroute to #{ARGV[0]} (#{probe.ip_dst})"
|
14
|
+
|
15
|
+
catch(:unreachable) do
|
16
|
+
( 1..64 ).each { |ttl|
|
17
|
+
( 1..3 ).each { |rep|
|
18
|
+
probe.ip_ttl = ttl
|
19
|
+
# some boxes refuse to reply to additional probes to the same uh_dport.
|
20
|
+
probe.uh_dport = 33434 + ttl + rep
|
21
|
+
packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,probe) ])
|
22
|
+
response = (packets[0].response) ? packets[0].response.packet : nil
|
23
|
+
if(response) then
|
24
|
+
puts '%d %s %5.3f ms' % [ ttl, response.ip_src, packets[0].rtt * 1000.0 ]
|
25
|
+
if(response.is_a?(Scriptroute::ICMP)) then
|
26
|
+
unreach = true if(response.icmp_type == Scriptroute::ICMP::ICMP_UNREACH)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
puts '%d *' % [ ttl ]
|
30
|
+
end
|
31
|
+
$stdout.flush
|
32
|
+
}
|
33
|
+
throw :unreachable if(unreach)
|
34
|
+
}
|
35
|
+
end
|
data/bin/tulip
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
# require "srclient";
|
4
|
+
require "scriptroute";
|
5
|
+
require "scriptroute/tulip/helper.rb";
|
6
|
+
require "scriptroute/tulip/reordering.rb";
|
7
|
+
require "scriptroute/tulip/loss.rb";
|
8
|
+
require "scriptroute/tulip/queuing.rb";
|
9
|
+
|
10
|
+
FIXCLOCK = "/usr/bin/awk -f scriptroute/fixclock";
|
11
|
+
|
12
|
+
################### defaults ################
|
13
|
+
|
14
|
+
$count = 1000;
|
15
|
+
$lag = 1000;
|
16
|
+
$spread = 0;
|
17
|
+
$start = 1;
|
18
|
+
$end = 30;
|
19
|
+
|
20
|
+
$skipRTrtrs = true;
|
21
|
+
$printFrequency = -1;
|
22
|
+
$allhops = true;
|
23
|
+
$dstonly = false;
|
24
|
+
$nonames = false;
|
25
|
+
$prefixpath = 0;
|
26
|
+
|
27
|
+
$verbose = 0;
|
28
|
+
$version = "0.0.1"
|
29
|
+
|
30
|
+
MAX_TRAIN = 4000;
|
31
|
+
|
32
|
+
#################### usage ##################
|
33
|
+
def usage()
|
34
|
+
puts "usage: tulip <reordering|loss|queuing> [options] <destination>\n";
|
35
|
+
puts "options:";
|
36
|
+
puts "--count <num>: number of measurements to use [#{$count}]";
|
37
|
+
puts "--lag <lag>: milliseconds to wait between successive measurements of the path [#{$lag}]";
|
38
|
+
puts "--spread <spread>: millisecond separation between probes in a single measurement [#{$spread}]";
|
39
|
+
puts "--start <start>: hop number to start diagnosis from [#{$start}]";
|
40
|
+
puts "--end <end>: hop number to end diagnosis at [#{$end}]";
|
41
|
+
puts "";
|
42
|
+
puts "--noskip: stop querying rate-limiting routers [skipping]";
|
43
|
+
puts "--mode <parallel|binary>: binary search not yet implemented";
|
44
|
+
puts "--printfrequency <frequency>: number of measurements to print the analysis [at the end]";
|
45
|
+
puts "--noallhops: use round trip measurements for hops that don't support forward primitives [on]";
|
46
|
+
puts "--dstonly: only probe end to end path [off]";
|
47
|
+
puts "--nonames: don't resolve ip addresses [resolve]";
|
48
|
+
puts "--prefixpath <hops>: number of initial hops to excuse for local load balancing [#{$prefixpath}]";
|
49
|
+
puts "";
|
50
|
+
puts "--help: display this message";
|
51
|
+
puts "--verbose <level>: level of verbosity (0-10) [#{$verbose}]";
|
52
|
+
puts "--version: print version information and exit";
|
53
|
+
puts "";
|
54
|
+
exit 1;
|
55
|
+
end
|
56
|
+
|
57
|
+
def printVersionAndExit()
|
58
|
+
puts "tulip version: #{$version}";
|
59
|
+
exit 0;
|
60
|
+
end
|
61
|
+
|
62
|
+
#################### command line processing #############
|
63
|
+
|
64
|
+
printVersionAndExit() if (ARGV.index("--version"));
|
65
|
+
usage() if (ARGV.length < 2);
|
66
|
+
|
67
|
+
pathology = ARGV.shift();
|
68
|
+
if (pathology[0,1] == "r") then
|
69
|
+
pathology = "reordering";
|
70
|
+
elsif (pathology[0,1] == "l") then
|
71
|
+
pathology = "loss";
|
72
|
+
elsif (pathology[0,1] == "q") then
|
73
|
+
pathology = "queuing";
|
74
|
+
else
|
75
|
+
puts "ERROR: unknown pathology";
|
76
|
+
usage();
|
77
|
+
end
|
78
|
+
|
79
|
+
switch = ARGV.shift();
|
80
|
+
while (switch and switch[0,2] == "--")
|
81
|
+
if (switch == "--count") then $count = ARGV.shift.to_i;
|
82
|
+
elsif (switch == "--lag") then $lag = ARGV.shift.to_i;
|
83
|
+
elsif (switch == "--spread") then $spread = ARGV.shift.to_f;
|
84
|
+
elsif (switch == "--start") then $start = ARGV.shift.to_i;
|
85
|
+
elsif (switch == "--end") then $end = ARGV.shift.to_i;
|
86
|
+
elsif (switch == "--printfrequency") then $printFrequency = ARGV.shift.to_i;
|
87
|
+
elsif (switch == "--help") then usage();
|
88
|
+
elsif (switch == "--verbose") then $verbose = ARGV.shift.to_i;
|
89
|
+
elsif (switch == "--noskip") then $skipRTrtrs = false;
|
90
|
+
elsif (switch == "--noallhops") then $allhops = false;
|
91
|
+
elsif (switch == "--dstonly") then $dstonly = true;
|
92
|
+
elsif (switch == "--nonames") then $nonames = true;
|
93
|
+
elsif (switch == "--prefixpath") then $prefixpath = ARGV.shift.to_i;
|
94
|
+
else
|
95
|
+
puts "ERROR: unknown option\n";
|
96
|
+
usage();
|
97
|
+
end
|
98
|
+
switch = ARGV.shift();
|
99
|
+
end
|
100
|
+
|
101
|
+
Scriptroute::daemon_running_or_exit
|
102
|
+
|
103
|
+
begin
|
104
|
+
destination = IPSocket.getaddress(switch) or switch;
|
105
|
+
rescue SocketError
|
106
|
+
puts "ERROR: unknown destination '#{switch}'";
|
107
|
+
usage();
|
108
|
+
end
|
109
|
+
|
110
|
+
if ($verbose>0) then
|
111
|
+
puts "pathology=%s count=%d lag=%d spread=%d start=%d end=%d verbose=%d destination=%s allhops=%s" % [pathology, $count, $lag, $spread, $start, $end, $verbose, destination, $allhops];
|
112
|
+
end
|
113
|
+
|
114
|
+
########## determine hop characteristics #########
|
115
|
+
|
116
|
+
puts "tracing to #{destination} ......" if ($verbose >= 1);
|
117
|
+
tpath = Scriptroute::Tulip::TracePath.new(destination, !$nonames);
|
118
|
+
|
119
|
+
#todo: get rid of this ugly hack
|
120
|
+
$global_tpath = tpath;
|
121
|
+
|
122
|
+
puts " ----- pathto #{destination} ----- \n#{tpath.to_s}\n";
|
123
|
+
if (tpath.status_code == "incomplete") then
|
124
|
+
puts "incomplete trace\n";
|
125
|
+
end
|
126
|
+
|
127
|
+
puts "discovering routers that support forward path diagnosis .....\n" if ($verbose >=1);
|
128
|
+
hopDetails = Array.new();
|
129
|
+
if ($dstonly) then
|
130
|
+
hopDetails[tpath.path.length-1] = [destination, 255, "udp", true, destination];
|
131
|
+
else
|
132
|
+
middle = $start;
|
133
|
+
while (middle <= [$end, tpath.path.length - 1].min)
|
134
|
+
|
135
|
+
router, ttl = tpath.path[middle].ip, tpath.path[middle].hop;
|
136
|
+
|
137
|
+
## for loss and reordering (ip-ids) ##
|
138
|
+
if (pathology == "reordering" || pathology == "loss") then
|
139
|
+
if (Scriptroute::Tulip::LangChecker.increasingIPIDs(destination, ttl, "udp")) then
|
140
|
+
hopDetails[middle] = [destination, ttl, "udp", true, router];
|
141
|
+
else
|
142
|
+
subpath = Scriptroute::Tulip::TracePath.new(router);
|
143
|
+
subset = tpath.subset(subpath);
|
144
|
+
puts " ----- subpathto #{router} (#{middle}) is #{subset} -----\n#{subpath.to_s}\n" if ($verbose>0);
|
145
|
+
if (subset) then
|
146
|
+
type, canCheckForward = Scriptroute::Tulip::LangChecker.getBestOption4IPIDs(router, "echo", "udp", "tcp", "tstamp");
|
147
|
+
if (canCheckForward) then
|
148
|
+
hopDetails[middle] = [router, 255, type, true, router]
|
149
|
+
else
|
150
|
+
hopDetails[middle] = [destination, ttl, "udp", false, router] if ($allhops or middle == tpath.path.length - 1);
|
151
|
+
end
|
152
|
+
else
|
153
|
+
hopDetails[middle] = [destination, ttl, "udp", false, router] if ($allhops or middle == tpath.path.length - 1);
|
154
|
+
end
|
155
|
+
end
|
156
|
+
## queuing (timestamps)
|
157
|
+
else
|
158
|
+
if Scriptroute::Tulip::(LangChecker.getBestOption(router, "tstamp")) then
|
159
|
+
subpath = Scriptroute::Tulip::TracePath.new(router);
|
160
|
+
subset = tpath.subset(subpath);
|
161
|
+
puts " ----- subpathto #{router} (#{middle}) is #{subset} -----\n#{subpath.to_s}\n" if ($verbose>0);
|
162
|
+
if (subset) then
|
163
|
+
hopDetails[middle] = [router, 255, "tstamp", true, router];
|
164
|
+
else
|
165
|
+
hopDetails[middle] = [router, 255, "tstamp", false, router] if ($allhops or middle == tpath.path.length - 1);
|
166
|
+
end
|
167
|
+
else
|
168
|
+
hopDetails[middle] = [destination, ttl, "udp", false, router] if ($allhops or middle == tpath.path.length - 1);
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
middle+=1;
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
(1..hopDetails.length-1).map { |i| puts "hopdetails: #{tpath.path[i].hop}. #{hopDetails[i].join(" ")}" if (hopDetails[i])} if ($verbose>0);
|
178
|
+
|
179
|
+
|
180
|
+
if (pathology == "reordering") then Scriptroute::Tulip::ReorderingDoctor.new(hopDetails);
|
181
|
+
elsif (pathology == "loss") then Scriptroute::Tulip::LossDoctor.new(hopDetails);
|
182
|
+
else Scriptroute::Tulip::QueuingDoctor.new(hopDetails);
|
183
|
+
end
|
data/lib/scriptroute.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
# @author Neil Spring
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'base64'
|
6
|
+
require 'resolv'
|
7
|
+
require 'scriptroute/packets'
|
8
|
+
require 'scriptroute/ally'
|
9
|
+
require 'scriptroute/rockettrace'
|
10
|
+
require 'scriptroute/nameify'
|
11
|
+
|
12
|
+
module Scriptroute
|
13
|
+
|
14
|
+
class ScriptrouteError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# A ScriptrouteConnection is an object that wraps the TCP
|
18
|
+
# socket used to connect to a scriptroute daemon. It likely
|
19
|
+
# need not be used directly, unless accessing low level
|
20
|
+
# scriptroute commands not wrapped by the general
|
21
|
+
# Scriptroute module
|
22
|
+
class ScriptrouteConnection
|
23
|
+
Client_name = "%s(%d)" % [ defined?(Etc) ? Etc.getlogin : ENV['USER'], Process.uid ]
|
24
|
+
# this version is designed to emulate the 0.4.14 version of srinterpreter.
|
25
|
+
VERSION = '0.4.14'
|
26
|
+
def initialize
|
27
|
+
begin
|
28
|
+
timeout(5) do
|
29
|
+
@s = TCPSocket.new 'localhost', 3356
|
30
|
+
end
|
31
|
+
reply = ""
|
32
|
+
reply = one_line_command( "interpret v%s %s\n" % [ VERSION, Client_name ] )
|
33
|
+
if reply =~ /proceed( Hz=(\d+))/ then
|
34
|
+
# puts "negotiated."
|
35
|
+
else
|
36
|
+
puts "failed to negotiate: %s" % reply
|
37
|
+
end
|
38
|
+
rescue Errno::ECONNREFUSED => e
|
39
|
+
$stderr.puts "Connection refused connecting to scriptrouted, ensure that it is running"
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# @param t [String] the command to issue, newline optional
|
44
|
+
# @return [String, nil] the one-line reply, or nil if the connection timed out or was terminated.
|
45
|
+
def one_line_command(t)
|
46
|
+
begin
|
47
|
+
reply = nil
|
48
|
+
timeout(2) do
|
49
|
+
issue_command t
|
50
|
+
reply = @s.readline
|
51
|
+
end
|
52
|
+
rescue EOFError
|
53
|
+
end
|
54
|
+
return reply
|
55
|
+
end
|
56
|
+
# @return [Hash] the configuration of the scriptroute daemon
|
57
|
+
def get_config
|
58
|
+
ret = Hash.new
|
59
|
+
timeout(5) do
|
60
|
+
issue_command "showconfig\n"
|
61
|
+
l = "do/while"
|
62
|
+
# showconfig ends reply with an empty line.
|
63
|
+
while l !~ /^#done/ && l !~ /^$/
|
64
|
+
l = @s.readline
|
65
|
+
if l =~ /^(\S+)\s+=\s+(.+)$/ then
|
66
|
+
ret[$1] = $2
|
67
|
+
elsif l =~ /^(\S+)\s+=\s*$/ then
|
68
|
+
ret[$1] = nil
|
69
|
+
elsif l =~ /^$/ then
|
70
|
+
# end.
|
71
|
+
else
|
72
|
+
puts "unparsed config: %s" % l
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
ret
|
77
|
+
end
|
78
|
+
# @param t [String] the command to issue, newline optional
|
79
|
+
def issue_command(t)
|
80
|
+
# $stderr.puts "writing %s" % t
|
81
|
+
if t[-1] == "\n" then
|
82
|
+
@s.write t
|
83
|
+
else
|
84
|
+
@s.write "%s\n" % [ t ]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
# @return [String,nil] A one-line reply, or nil if the connection terminated.
|
88
|
+
def get_reply
|
89
|
+
begin
|
90
|
+
@s.readline
|
91
|
+
rescue EOFError
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# A DelayedPacket is for constructing trains in send_train.
|
98
|
+
Struct.new("DelayedPacket", :delay, :packet)
|
99
|
+
|
100
|
+
# TimedPacket is a time, packet tuple, with a tsc value thrown in in case its useful
|
101
|
+
class TimedPacket
|
102
|
+
# @return [Float,nil]
|
103
|
+
attr_accessor :time
|
104
|
+
# @return [Fixnum,nil] The output of rdtsc can be a more
|
105
|
+
# useful value in calculating rtt when NTP's adjustments
|
106
|
+
# via skew cause trouble.
|
107
|
+
attr_accessor :tsc
|
108
|
+
# @return [IPv4]
|
109
|
+
attr_accessor :packet
|
110
|
+
|
111
|
+
# @param time [Float,nil] Seconds since the epoch, or nil if we didn't see the packet leave due to pcap (happens)
|
112
|
+
# @param tsc [Fixnum,nil] Value of the cycle counter (rdtsc) or nil if not supported
|
113
|
+
# @param packet [IPv4] The packet received.
|
114
|
+
def initialize(time, tsc, packet)
|
115
|
+
raise ArgumentError, "no packet" unless packet
|
116
|
+
raise ArgumentError, "packet of the wrong class" unless packet.is_a?(IPv4)
|
117
|
+
@time = time
|
118
|
+
@tsc = tsc
|
119
|
+
@packet = packet
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# A ProbeResponse is a pair of a probe and its response.
|
124
|
+
# Scriptroute is designed around the idea that a general
|
125
|
+
# purpose engine can recognize the response to any probe,
|
126
|
+
# and be in charge of doing so, so that measurement tools
|
127
|
+
# need not have the rights to look at every packet.
|
128
|
+
#
|
129
|
+
# This design does limit somewhat, since probes that are
|
130
|
+
# capable of soliciting more than one response (e.g., via
|
131
|
+
# fragmentation) will not be managed properly.
|
132
|
+
class ProbeResponse
|
133
|
+
# @return [TimedPacket]
|
134
|
+
attr_accessor :probe
|
135
|
+
# @return [TimedPacket]
|
136
|
+
attr_accessor :response
|
137
|
+
# @return [Float,nil] Provides the apparent round trip time of this probe-response pair, or nil if either time is missing.
|
138
|
+
def rtt
|
139
|
+
if response and probe and probe.time then
|
140
|
+
response.time - probe.time
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
private
|
147
|
+
|
148
|
+
# Connection pool in case the script requires more than one connection to the server (for concurrent tests).
|
149
|
+
class ConnectionPool
|
150
|
+
@@connections_cache = []
|
151
|
+
# Connection pool mutex.
|
152
|
+
@@connections_mutex = Mutex.new
|
153
|
+
# Fetch from the connection pool or create a new connection
|
154
|
+
# @return [ScriptrouteConnection]
|
155
|
+
def ConnectionPool.get_idle_connection
|
156
|
+
@@connections_mutex.lock
|
157
|
+
if @@connections_cache.empty? then
|
158
|
+
@@connections_mutex.unlock
|
159
|
+
return ScriptrouteConnection.new
|
160
|
+
else
|
161
|
+
ret = @@connections_cache.shift
|
162
|
+
@@connections_mutex.unlock
|
163
|
+
return ret
|
164
|
+
end
|
165
|
+
end
|
166
|
+
# @param c [ScriptrouteConnection] the connection to return to the pool
|
167
|
+
# @return [void]
|
168
|
+
def ConnectionPool.return_idle_connection(c)
|
169
|
+
@@connections_mutex.synchronize {
|
170
|
+
@@connections_cache.push(c)
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
public
|
175
|
+
# Take a block to be executed with a ScriptrouteConnection
|
176
|
+
# from the pool. This is the function to use, since it
|
177
|
+
# manages the pooled connections explicitly. A leak in
|
178
|
+
# connections would be bad.
|
179
|
+
# @yield [ScriptrouteConnection] the connection for this block.
|
180
|
+
# @return the output of the block
|
181
|
+
def Scriptroute::with_scriptroute_connection
|
182
|
+
c = ConnectionPool.get_idle_connection
|
183
|
+
r = yield c
|
184
|
+
ConnectionPool.return_idle_connection(c)
|
185
|
+
r
|
186
|
+
end
|
187
|
+
# Check if the daemon is running by making a connection or
|
188
|
+
# checking the pool for an unused but working connection.
|
189
|
+
# @return [Boolean]
|
190
|
+
def Scriptroute::is_daemon_running?
|
191
|
+
begin
|
192
|
+
with_scriptroute_connection do |c|
|
193
|
+
end
|
194
|
+
return true
|
195
|
+
rescue Errno::ECONNREFUSED
|
196
|
+
return false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
# Exit failure if the scriptroute daemon does not appear
|
200
|
+
# to be running. This is useful at the beginning of
|
201
|
+
# scripts that expect the daemon to run, to avoid adding
|
202
|
+
# extra error checking later.
|
203
|
+
# @return [true] if the script does ot exit because the daemon
|
204
|
+
# is not running.
|
205
|
+
def Scriptroute::daemon_running_or_exit
|
206
|
+
begin
|
207
|
+
with_scriptroute_connection do |c|
|
208
|
+
end
|
209
|
+
return true
|
210
|
+
rescue Errno::ECONNREFUSED
|
211
|
+
puts "Ensure that the scriptroute daemon is running and try again"
|
212
|
+
exit 1
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# Query for the version of the daemon; useful if there's a
|
216
|
+
# feature only supported in a particular version. @return
|
217
|
+
# @return [String] the version string provided by of the running daemon
|
218
|
+
def Scriptroute::DaemonVersion
|
219
|
+
with_scriptroute_connection do |c|
|
220
|
+
puts c.one_line_command("version\n")
|
221
|
+
end
|
222
|
+
end
|
223
|
+
# @return [String] An IP address associated with the
|
224
|
+
# hostname. Currently just invokes Resolv.getaddress.
|
225
|
+
def Scriptroute::dns_lookup(name)
|
226
|
+
Resolv.getaddress name
|
227
|
+
end
|
228
|
+
# @return [Hash] the configuration of the running daemon,
|
229
|
+
# useful for checking rate limiting parameters or filters
|
230
|
+
# if a specially configured daemon is needed.
|
231
|
+
def Scriptroute::DaemonConfig
|
232
|
+
ret = nil
|
233
|
+
with_scriptroute_connection do |c|
|
234
|
+
ret = c.get_config
|
235
|
+
end
|
236
|
+
ret
|
237
|
+
end
|
238
|
+
# no op for backward compatibility with the srinterpreter version
|
239
|
+
# that had a dedicated packet instance
|
240
|
+
# @param s [String] A marshaled packet.
|
241
|
+
# @return [String] The input parameter, which is sufficient for this implementation of {Scriptroute::send_train}.
|
242
|
+
def Scriptroute::pkt_from_string(s)
|
243
|
+
s
|
244
|
+
end
|
245
|
+
# This is the nut.
|
246
|
+
# @param train [Array<Struct::DelayedPacket,Array>] an array of Struct::DelayedPacket entries or arrays containing delay, packet pairs.
|
247
|
+
# @return [Array<Scriptroute::ProbeResponse>] an array of Scriptroute::ProbeResponse entries, comprising a probe and response.
|
248
|
+
def Scriptroute::send_train(train)
|
249
|
+
ret = nil
|
250
|
+
return [] if train.empty? # easy?
|
251
|
+
with_scriptroute_connection do |c|
|
252
|
+
# issue the send train command.
|
253
|
+
i = 1;
|
254
|
+
total_delay = 0
|
255
|
+
train.map! { |boxcar|
|
256
|
+
raise ArgumentError, 'nil entry' unless boxcar
|
257
|
+
if boxcar.is_a?(Array) then
|
258
|
+
Struct::DelayedPacket.new(*boxcar)
|
259
|
+
else
|
260
|
+
raise ArgumentError, 'not a boxcar or an array' unless boxcar.is_a?(Struct::DelayedPacket)
|
261
|
+
boxcar
|
262
|
+
end
|
263
|
+
}
|
264
|
+
train.each { |boxcar|
|
265
|
+
# needs validation.
|
266
|
+
delay = boxcar[:delay]
|
267
|
+
packet = boxcar[:packet]
|
268
|
+
raise "no packet" unless packet
|
269
|
+
if packet.is_a?(Scriptroute::IPv4) then
|
270
|
+
packet = packet.marshal
|
271
|
+
end
|
272
|
+
raise "packet is #{packet.class}, not a string" unless packet.is_a?(String)
|
273
|
+
encoded_packet = Base64.strict_encode64(packet) # strict_encode means no line feeds added.
|
274
|
+
c.issue_command "sendtrain %d/%d %f %d %s\n" % [ i, train.length, delay<0 ? 0 : delay, encoded_packet.length, encoded_packet ]
|
275
|
+
i+=1
|
276
|
+
total_delay += delay
|
277
|
+
}
|
278
|
+
|
279
|
+
# allocate space for the responses, array of probe response pairs, each of which will be a time/packet pair.
|
280
|
+
ret = Array.new(i-1) { ProbeResponse.new }
|
281
|
+
|
282
|
+
# collect, waiting maybe 5 minutes more than needed just in case the daemon hangs on us.
|
283
|
+
begin
|
284
|
+
timeout(total_delay + 300) do
|
285
|
+
while l = c.get_reply and l !~ /^done/ and l =~ /\S+/ do
|
286
|
+
if l =~ /^ERROR/ then
|
287
|
+
# have to throw an exception, although individual packet errors are possible, that's
|
288
|
+
# not communicated by the daemon.
|
289
|
+
$stderr.puts l
|
290
|
+
if l=~ /disjoint train \missing packet #(\d+)/ then
|
291
|
+
ret[ $1.to_i ].probe = nil
|
292
|
+
ret[ $1.to_i ].response = nil
|
293
|
+
end
|
294
|
+
raise ScriptrouteError, l
|
295
|
+
else
|
296
|
+
if l =~ /^(-?\d+\.\d+)\/(\d+) (\d+)([<>]) \d+ (\S+)$/ then
|
297
|
+
tv_s = $1.to_f
|
298
|
+
rdtsc = $2
|
299
|
+
pk = $3.to_i - 1
|
300
|
+
io = ($4 == '>') ? 0 : 1
|
301
|
+
st = $5
|
302
|
+
packet_string = Base64.strict_decode64(st)
|
303
|
+
p = IPv4.creator(packet_string)
|
304
|
+
tv_s = nil if tv_s < 0 # didn't see it leave.
|
305
|
+
tp = TimedPacket.new(tv_s, rdtsc, p)
|
306
|
+
if io == 0 then
|
307
|
+
ret[ pk ].probe = tp
|
308
|
+
else
|
309
|
+
ret[ pk ].response = tp
|
310
|
+
end
|
311
|
+
else
|
312
|
+
puts "unparsed send_train response: #{l}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end # while
|
316
|
+
end # timeout
|
317
|
+
rescue TimeoutError
|
318
|
+
$stderr.puts "timed out parsing responses for send_train"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
return ret
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Local Variables:
|
326
|
+
# compile-command: "rake test"
|
327
|
+
# End:
|