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
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:
|