scriptroute 0.4.14 → 0.4.15
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 +4 -4
- data/bin/sr-traceroute6 +37 -0
- data/lib/scriptroute.rb +13 -8
- data/lib/scriptroute/ally.rb +1 -1
- data/lib/scriptroute/ipaddr4.rb +58 -0
- data/lib/scriptroute/packets.rb +243 -26
- data/test/test_commando.rb +26 -0
- data/test/test_scriptroute.rb +167 -11
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e286d4b4fa4a450c8a2aaac1fe6987ae1ac8ed8b
|
4
|
+
data.tar.gz: af98fdafbc90ad8b9b528b0104851f3e5465e88e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab994b7c31ffdc3b72622689776bd70cd3545bd8abfe6c046112130cbae57874773b3c28b399206e1fbf59079bd1cefd1573bcaa5eee244f2f88d6826884a5f0
|
7
|
+
data.tar.gz: f8f93fc4ef3c998d1a622e476ff1a414bce2fd3ace0720ac7a83f8e2ad44c8bb006d895c7aeced6cad670eeeea07f8c0eca8a534827d5404e5465199845a8ff9
|
data/bin/sr-traceroute6
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'scriptroute'
|
4
|
+
require 'scriptroute/packets'
|
5
|
+
|
6
|
+
if ARGV.empty? then
|
7
|
+
puts "usage: #{$0} address_or_hostname"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
probe = Scriptroute::UDP6.new(12)
|
12
|
+
|
13
|
+
probe.ip6_dst = ARGV[0]
|
14
|
+
|
15
|
+
unreach = false
|
16
|
+
|
17
|
+
puts "Traceroute to #{ARGV[0]} (#{probe.ip6_dst})"
|
18
|
+
|
19
|
+
catch(:unreachable) do
|
20
|
+
( 1..64 ).each { |ttl|
|
21
|
+
( 1..3 ).each { |rep|
|
22
|
+
probe.ip_ttl = ttl
|
23
|
+
packets = Scriptroute::send_train([ Struct::DelayedPacket.new(0,probe) ])
|
24
|
+
response = (packets[0].response) ? packets[0].response.packet : nil
|
25
|
+
if(response) then
|
26
|
+
puts '%d %s %5.3f ms' % [ ttl, response.ip6_src, packets[0].rtt * 1000.0 ]
|
27
|
+
if(response.is_a?(Scriptroute::ICMP6)) then
|
28
|
+
unreach = true if(response.icmp_type == Scriptroute::ICMP6::ICMP6_DST_UNREACH)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
puts ttl.to_s + ' *'
|
32
|
+
end
|
33
|
+
$stdout.flush
|
34
|
+
}
|
35
|
+
throw :unreachable if(unreach)
|
36
|
+
}
|
37
|
+
end
|
data/lib/scriptroute.rb
CHANGED
@@ -105,15 +105,15 @@ end
|
|
105
105
|
# useful value in calculating rtt when NTP's adjustments
|
106
106
|
# via skew cause trouble.
|
107
107
|
attr_accessor :tsc
|
108
|
-
# @return [
|
108
|
+
# @return [IP]
|
109
109
|
attr_accessor :packet
|
110
110
|
|
111
111
|
# @param time [Float,nil] Seconds since the epoch, or nil if we didn't see the packet leave due to pcap (happens)
|
112
112
|
# @param tsc [Fixnum,nil] Value of the cycle counter (rdtsc) or nil if not supported
|
113
|
-
# @param packet [
|
113
|
+
# @param packet [IP] The packet received.
|
114
114
|
def initialize(time, tsc, packet)
|
115
115
|
raise ArgumentError, "no packet" unless packet
|
116
|
-
raise ArgumentError, "packet of the wrong class" unless packet.is_a?(
|
116
|
+
raise ArgumentError, "packet of the wrong class" unless packet.is_a?(IP)
|
117
117
|
@time = time
|
118
118
|
@tsc = tsc
|
119
119
|
@packet = packet
|
@@ -225,15 +225,20 @@ end
|
|
225
225
|
def Scriptroute::dns_lookup(name)
|
226
226
|
Resolv.getaddress name
|
227
227
|
end
|
228
|
+
private
|
229
|
+
@@config = nil
|
230
|
+
public
|
228
231
|
# @return [Hash] the configuration of the running daemon,
|
229
232
|
# useful for checking rate limiting parameters or filters
|
230
233
|
# if a specially configured daemon is needed.
|
231
234
|
def Scriptroute::DaemonConfig
|
232
235
|
ret = nil
|
233
|
-
|
234
|
-
|
236
|
+
unless @@config then
|
237
|
+
with_scriptroute_connection do |c|
|
238
|
+
@@config = c.get_config
|
239
|
+
end
|
235
240
|
end
|
236
|
-
|
241
|
+
@@config
|
237
242
|
end
|
238
243
|
# no op for backward compatibility with the srinterpreter version
|
239
244
|
# that had a dedicated packet instance
|
@@ -266,7 +271,7 @@ end
|
|
266
271
|
delay = boxcar[:delay]
|
267
272
|
packet = boxcar[:packet]
|
268
273
|
raise "no packet" unless packet
|
269
|
-
if packet.is_a?(Scriptroute::
|
274
|
+
if packet.is_a?(Scriptroute::IP) then
|
270
275
|
packet = packet.marshal
|
271
276
|
end
|
272
277
|
raise "packet is #{packet.class}, not a string" unless packet.is_a?(String)
|
@@ -300,7 +305,7 @@ end
|
|
300
305
|
io = ($4 == '>') ? 0 : 1
|
301
306
|
st = $5
|
302
307
|
packet_string = Base64.strict_decode64(st)
|
303
|
-
p =
|
308
|
+
p = IP.creator(packet_string)
|
304
309
|
tv_s = nil if tv_s < 0 # didn't see it leave.
|
305
310
|
tp = TimedPacket.new(tv_s, rdtsc, p)
|
306
311
|
if io == 0 then
|
data/lib/scriptroute/ally.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
# require 'my-ipaddr'
|
3
|
+
|
4
|
+
class IPAddr4 < IPAddr
|
5
|
+
# subclass that trusts its initializer and assumes IPv4
|
6
|
+
def in_addr(addr)
|
7
|
+
if addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
|
8
|
+
(($1.to_i << 24) + ($2.to_i << 16) + ($3.to_i << 8) + ($4.to_i))
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
def initialize(addr)
|
14
|
+
@family = Socket::AF_INET
|
15
|
+
if( addr.kind_of?(String) ) then
|
16
|
+
prefix, len = addr.split('/')
|
17
|
+
@addr = in_addr(prefix)
|
18
|
+
if(len) then
|
19
|
+
mask_len!(len.to_i)
|
20
|
+
else
|
21
|
+
@mask_addr = IN4MASK
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@addr = addr
|
25
|
+
@mask_addr = IN4MASK
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# faster include method that only works for ipv4.
|
29
|
+
def include?(other)
|
30
|
+
if other.kind_of?(IPAddr)
|
31
|
+
other_addr = other.to_i
|
32
|
+
else # Not IPAddr - assume integer in same family as us
|
33
|
+
other_addr = other.to_i
|
34
|
+
end
|
35
|
+
return ((@addr & @mask_addr) == (other_addr & @mask_addr))
|
36
|
+
end
|
37
|
+
def eql?(other)
|
38
|
+
if other.kind_of?(IPAddr4) && @family != other.family
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
return (@addr == other.to_i)
|
42
|
+
end
|
43
|
+
def hash
|
44
|
+
@addr.to_i
|
45
|
+
end
|
46
|
+
def to_s
|
47
|
+
# about twice as fast as the map/join based to_s.
|
48
|
+
# appears slightly faster than mask then shift.
|
49
|
+
"%d.%d.%d.%d" % [ (@addr >> 24) & 0xff, (@addr >> 16) & 0xff, (@addr >> 8) & 0xff, (0xff&@addr) ]
|
50
|
+
end
|
51
|
+
def ==(other)
|
52
|
+
if other.kind_of?(IPAddr4)
|
53
|
+
@addr == other.addr
|
54
|
+
else
|
55
|
+
@addr == other.to_i # dunno why.
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/scriptroute/packets.rb
CHANGED
@@ -2,10 +2,140 @@ module Scriptroute
|
|
2
2
|
# a small library of routines for constructing packets
|
3
3
|
# as strings to hand off to the interpreter.
|
4
4
|
|
5
|
-
class
|
6
|
-
|
7
|
-
IPPROTO_TCP = 6
|
5
|
+
class IP
|
6
|
+
IPPROTO_TCP = 6 # apply to both 4 and 6.
|
8
7
|
IPPROTO_UDP = 17
|
8
|
+
def to_bytes
|
9
|
+
marshal
|
10
|
+
end
|
11
|
+
def IP.creator(str)
|
12
|
+
ip_vhl = str.unpack("c")[0]
|
13
|
+
ip_v = ((ip_vhl & 0xf0) >> 4)
|
14
|
+
if(ip_v == 4) then
|
15
|
+
IPv4.creator(str)
|
16
|
+
elsif(ip_v == 6) then
|
17
|
+
IPv6.creator(str)
|
18
|
+
else
|
19
|
+
raise "unknown IP version #%d in %s" % [ ip_v, str.unpack("C*").map { |c| "%x" % c }.join(' ') ]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class IPv4 < IP
|
25
|
+
IPPROTO_ICMP = 1
|
26
|
+
end
|
27
|
+
|
28
|
+
class IPv6 < IP
|
29
|
+
IPPROTO_ICMP6 = 58
|
30
|
+
attr_accessor :ip6_src, :ip6_hlim, :ip6_flow
|
31
|
+
attr_reader :ip6_dst
|
32
|
+
@@creators = Hash.new
|
33
|
+
def IPv6.creator(str)
|
34
|
+
flow, plen, nxt, hlim, saddr, daddr = str.unpack("Nncca16a16")
|
35
|
+
if(@@creators[nxt]) then
|
36
|
+
pkt = (@@creators[nxt]).call(str[40..40+plen])
|
37
|
+
pkt.ipv6_unmarshal(str)
|
38
|
+
pkt
|
39
|
+
else
|
40
|
+
raise "unknown IPv6 next header #%d in %s" % [ nxt, str.unpack("H*") ]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def ipv6_unmarshal(str)
|
44
|
+
@ip6_flow, @ip6_plen, @ip6_nxt, @ip6_hlim, ip6_saddr, ip6_daddr = str.unpack("Nncca16a16")
|
45
|
+
@ip6_src, @ip6_dst = [ip6_saddr, ip6_daddr].map { |addr| IPAddr.new_ntoh(addr) }
|
46
|
+
end
|
47
|
+
def initialize(p)
|
48
|
+
if(p.is_a?(Fixnum)) then
|
49
|
+
@ip6_src = @ip6_dst = @ip6flow = nil
|
50
|
+
@ip6_hlim = 64
|
51
|
+
@ip6_nxt = p
|
52
|
+
# actually, unlikely 7 or 13 are at the moment
|
53
|
+
# either, but let's start here.
|
54
|
+
$stderr.puts "unlikely v6 protocol header type #{p} is supported" unless [ 7, 13, 58 ].include?(p)
|
55
|
+
else
|
56
|
+
raise "need a next header type for constructing a v6 packet"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# for simplicity.
|
60
|
+
def ip_ttl=(ttl)
|
61
|
+
@ip6_hlim = ttl
|
62
|
+
end
|
63
|
+
def ip6_dst=(name_or_address)
|
64
|
+
if name_or_address.is_a?(IPAddr) then
|
65
|
+
if name_or_address.ipv4? then
|
66
|
+
@ip6_dst = name_or_address.ipv4_mapped # try to use it.
|
67
|
+
else
|
68
|
+
@ip6_dst = name_or_address
|
69
|
+
end
|
70
|
+
elsif Resolv::IPv6::Regex =~ name_or_address then
|
71
|
+
@ip6_dst = IPAddr.new(name_or_address, Socket::AF_INET6)
|
72
|
+
else
|
73
|
+
r = Resolv::DNS.new.getresource(name_or_address, Resolv::DNS::Resource::IN::AAAA)
|
74
|
+
if r then
|
75
|
+
@ip6_dst = IPAddr.new(r.address.to_s, Socket::AF_INET6)
|
76
|
+
else
|
77
|
+
raise "failed to lookup #{name_or_address} into a v6 address"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
def marshal
|
82
|
+
# probably should force src/dst internal
|
83
|
+
# representation to be IPAddr via accessor methods
|
84
|
+
# rather than reconvert here.
|
85
|
+
puts "unlikely you wanted zero hlim" unless @ip6_hlim and @ip6_hlim > 0
|
86
|
+
puts "unlikely you wanted no next protocol" unless @ip6_nxt and @ip6_nxt > 0
|
87
|
+
puts "unlikely you wanted nil payload len" unless ip_payload_len
|
88
|
+
v6packet = [ 6 << 4, 0, 0, ip_payload_len, @ip6_nxt, @ip6_hlim,
|
89
|
+
IPAddr.new(@ip6_src, Socket::AF_INET6).hton,
|
90
|
+
IPAddr.new(@ip6_dst, Socket::AF_INET6).hton ].pack("ccnncca16a16")
|
91
|
+
end
|
92
|
+
def to_s
|
93
|
+
"%s -> %s hlim=%d" % [ (@ip6_src or "::0"), (@ip6_dst or "::0"), @ip6_hlim ]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
class ICMP6 < IPv6
|
97
|
+
attr_reader :icmp_type, :icmp_code, :icmp_cksum
|
98
|
+
attr_reader :ip_payload_len
|
99
|
+
# type
|
100
|
+
ICMP6_ECHO = 128
|
101
|
+
ICMP6_ECHOREPLY = 129
|
102
|
+
ICMP6_DST_UNREACH = 1
|
103
|
+
ICMP6_PACKET_TOO_BIG = 2
|
104
|
+
ICMP6_TIME_EXCEEDED = 3
|
105
|
+
ICMP6_PARAM_PROB = 4
|
106
|
+
|
107
|
+
@@icmp6_creators = Hash.new
|
108
|
+
@@creators[IPPROTO_ICMP6] = lambda { |hdr|
|
109
|
+
icmp_type, icmp_code, icmp_cksum = hdr.unpack("CCn")
|
110
|
+
if(@@icmp6_creators[icmp_type]) then
|
111
|
+
pkt = @@icmp6_creators[icmp_type].call(hdr)
|
112
|
+
else
|
113
|
+
raise "unknown icmp6 type #%d" % icmp_type
|
114
|
+
end
|
115
|
+
}
|
116
|
+
def initialize(type_or_str)
|
117
|
+
if(type_or_str.is_a?(Fixnum)) then
|
118
|
+
@icmp_type = type_or_str
|
119
|
+
@icmp_code = 0
|
120
|
+
@icmp_cksum = 0
|
121
|
+
super(IPPROTO_ICMP6)
|
122
|
+
else
|
123
|
+
@icmp_type, @icmp_code, @icmp_cksum = type_or_str.unpack("CCn")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
# @return [String] The packet in string form
|
127
|
+
def marshal
|
128
|
+
@icmp_type or raise "type is nil"
|
129
|
+
@icmp_code or raise "code is nil"
|
130
|
+
@icmp_cksum = 0
|
131
|
+
super + [ @icmp_type, @icmp_code, @icmp_cksum ].pack("CCn")
|
132
|
+
end
|
133
|
+
# @return [String]
|
134
|
+
def to_s
|
135
|
+
super + ": ICMP6: type %d code %d cksum %d" %[ @icmp_type, @icmp_code, @icmp_cksum ]
|
136
|
+
end
|
137
|
+
#instantiate echo instead.
|
138
|
+
private :marshal
|
9
139
|
end
|
10
140
|
|
11
141
|
class ICMP < IPv4
|
@@ -227,14 +357,22 @@ module Scriptroute
|
|
227
357
|
ip_id, ip_off,
|
228
358
|
ip_ttl, ip_p, ip_sum,
|
229
359
|
ip_src, ip_dst = str.unpack("ccn" + "nn" + "ccn" + "N" + "N");
|
230
|
-
|
231
|
-
if(
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
360
|
+
ip_v = ((ip_vhl & 0xf0) >> 4)
|
361
|
+
if(ip_v == 4) then
|
362
|
+
ip_hl = ip_vhl & 0xf;
|
363
|
+
if(@@creators[ip_p]) then
|
364
|
+
pkt = (@@creators[ip_p]).call(str[(ip_hl * 4) .. ip_len])
|
365
|
+
pkt.ipv4_unmarshal(str)
|
366
|
+
pkt
|
367
|
+
else
|
368
|
+
raise "unknown IPv4 protocol #%d in %s" % [ ip_p, str.unpack("C*").map { |c| "%x" % c }.join(' ') ]
|
369
|
+
end
|
370
|
+
elsif(ip_v == 6) then
|
371
|
+
# probably should do this someplace above IPv4 creator.
|
372
|
+
puts "received v6 packet %s" % [ str.unpack("H*") ]
|
373
|
+
str
|
236
374
|
else
|
237
|
-
raise "unknown
|
375
|
+
raise "unknown IP version #%d in %s" % [ ip_v, str.unpack("C*").map { |c| "%x" % c }.join(' ') ]
|
238
376
|
end
|
239
377
|
end
|
240
378
|
|
@@ -398,24 +536,14 @@ module Scriptroute
|
|
398
536
|
end
|
399
537
|
end
|
400
538
|
|
401
|
-
|
539
|
+
module UDPgeneric
|
402
540
|
# @return [Fixnum]
|
403
541
|
attr_reader :uh_sport, :uh_dport, :uh_ulen, :uh_sum
|
404
542
|
attr_writer :uh_dport, :uh_sum
|
405
543
|
|
406
|
-
@@creators[IPPROTO_UDP] = lambda { |hdr|
|
407
|
-
uh_sport, uh_dport, uh_ulen, uh_sum = hdr.unpack("nnnn")
|
408
|
-
if uh_sport==123 || uh_dport==123 then
|
409
|
-
p = NTP.new(hdr[8..hdr.length])
|
410
|
-
p.udp_unmarshal(hdr)
|
411
|
-
else
|
412
|
-
p = UDP.new(hdr)
|
413
|
-
end
|
414
|
-
p
|
415
|
-
}
|
416
|
-
|
417
544
|
# @return [Fixnum] the udp data plus header length, uh_ulen
|
418
545
|
def ip_payload_len
|
546
|
+
raise "ip_payload_len was nil when asked" unless @uh_ulen
|
419
547
|
@uh_ulen
|
420
548
|
end
|
421
549
|
|
@@ -431,7 +559,7 @@ module Scriptroute
|
|
431
559
|
@uh_sport = 32945
|
432
560
|
@uh_dport = 33434
|
433
561
|
@uh_sum = 0
|
434
|
-
super( IPPROTO_UDP )
|
562
|
+
super( Scriptroute::IP::IPPROTO_UDP )
|
435
563
|
else
|
436
564
|
@uh_sport, @uh_dport, @uh_ulen, @uh_sum = paylen_or_str.unpack("nnnn")
|
437
565
|
end
|
@@ -445,12 +573,11 @@ module Scriptroute
|
|
445
573
|
array_of_elements = [ @uh_sport, @uh_dport, @uh_ulen, @uh_sum ]
|
446
574
|
raise "a UDP header field was unset" if array_of_elements.include?(nil)
|
447
575
|
super + [ @uh_sport, @uh_dport, @uh_ulen, @uh_sum ].pack("nnnn") +
|
448
|
-
if ( self.class == UDP ) then
|
576
|
+
if ( self.class == UDP or self.class == UDP6 ) then
|
449
577
|
"\0" * ( @uh_ulen - 8 )
|
450
578
|
else
|
451
579
|
"" # the subclass will take care of it
|
452
580
|
end
|
453
|
-
|
454
581
|
end
|
455
582
|
|
456
583
|
# Used for subclasses to set the UDP header fields.
|
@@ -468,6 +595,29 @@ module Scriptroute
|
|
468
595
|
|
469
596
|
end
|
470
597
|
|
598
|
+
class UDP < IPv4
|
599
|
+
include UDPgeneric
|
600
|
+
|
601
|
+
@@creators[IPPROTO_UDP] = lambda { |hdr|
|
602
|
+
uh_sport, uh_dport, uh_ulen, uh_sum = hdr.unpack("nnnn")
|
603
|
+
if uh_sport==123 || uh_dport==123 then
|
604
|
+
p = NTP.new(hdr[8..hdr.length])
|
605
|
+
p.udp_unmarshal(hdr)
|
606
|
+
else
|
607
|
+
p = UDP.new(hdr)
|
608
|
+
end
|
609
|
+
p
|
610
|
+
}
|
611
|
+
end
|
612
|
+
|
613
|
+
class UDP6 < IPv6
|
614
|
+
include UDPgeneric
|
615
|
+
|
616
|
+
@@creators[IPPROTO_UDP] = lambda { |hdr|
|
617
|
+
UDP6.new(hdr)
|
618
|
+
}
|
619
|
+
end
|
620
|
+
|
471
621
|
class TCP < IPv4
|
472
622
|
attr_reader :th_sport, :th_dport, :th_sum, :th_seq, :th_ack,
|
473
623
|
:th_win, :th_flags, :th_win, :th_sum, :th_urp
|
@@ -584,6 +734,38 @@ module Scriptroute
|
|
584
734
|
super + ": ECHO: id %d seq %d len %d" % [ @icmp_id, @icmp_seq, @ip_payload_len ]
|
585
735
|
end
|
586
736
|
end
|
737
|
+
# Class for ping packets (echo and echo reply) much duplicated with ICMPecho
|
738
|
+
class ICMP6echo < ICMP6
|
739
|
+
# @return [Fixnum]
|
740
|
+
attr_reader :icmp_id, :icmp_seq
|
741
|
+
attr_writer :icmp_seq
|
742
|
+
@@icmp6_creators[ICMP6_ECHO] =
|
743
|
+
@@icmp6_creators[ICMP6_ECHOREPLY] = lambda { |hdr|
|
744
|
+
ICMP6echo.new(hdr)
|
745
|
+
}
|
746
|
+
def initialize(paylen_or_str = 0)
|
747
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
748
|
+
if( paylen_or_str < 0) then raise "payload length must be >= 0" end
|
749
|
+
@ip_payload_len = paylen_or_str + 4 + 4
|
750
|
+
@icmp_id = 666
|
751
|
+
@icmp_seq = 1
|
752
|
+
super(ICMP6_ECHO)
|
753
|
+
else
|
754
|
+
# x is skip forward a character.
|
755
|
+
@ip_payload_len = paylen_or_str.length - 8
|
756
|
+
@icmp_id, @icmp_seq = paylen_or_str.unpack("xxxxnn")
|
757
|
+
super(paylen_or_str)
|
758
|
+
end
|
759
|
+
end
|
760
|
+
# @return [String] The packet in string form
|
761
|
+
def marshal
|
762
|
+
super + [ @icmp_id, @icmp_seq ].pack("nn") + "\0" * ( @ip_payload_len - 4 - 4 )
|
763
|
+
end
|
764
|
+
# @return [String]
|
765
|
+
def to_s
|
766
|
+
super + ": ECHO6: id %d seq %d len %d" % [ @icmp_id, @icmp_seq, @ip_payload_len ]
|
767
|
+
end
|
768
|
+
end
|
587
769
|
|
588
770
|
class ICMPtstamp < ICMP
|
589
771
|
# @return [Fixnum]
|
@@ -666,6 +848,33 @@ module Scriptroute
|
|
666
848
|
end
|
667
849
|
end
|
668
850
|
|
851
|
+
class ICMP6unreach< ICMP6
|
852
|
+
# @return [IPv4] The packet header embedded within the ICMP unreachable error message.
|
853
|
+
attr_reader :contents
|
854
|
+
@@icmp6_creators[ICMP6_DST_UNREACH] = @@icmp6_creators[ICMP6_TIME_EXCEEDED] =
|
855
|
+
lambda { |hdr|
|
856
|
+
ICMP6unreach.new(hdr)
|
857
|
+
}
|
858
|
+
# Can create an unreachable only from string contents, never from filling in fields given a size.
|
859
|
+
# param string [String] the contents of the received packet.
|
860
|
+
def initialize(string)
|
861
|
+
# first four are code, type, checksum.
|
862
|
+
# second four are undefined
|
863
|
+
@contents = IPv6.creator(string[8..-1])
|
864
|
+
super(string)
|
865
|
+
end
|
866
|
+
# Cannot marshal an unreachable packet for transmission; raises an exception.
|
867
|
+
# @return [void]
|
868
|
+
def marshal
|
869
|
+
raise "not supported"
|
870
|
+
end
|
871
|
+
# @return [String] formats the packet and the embedded packet as a string.
|
872
|
+
def to_s
|
873
|
+
super + " ( " + @contents.to_s + " )"
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
|
669
878
|
class NTP < UDP
|
670
879
|
# @return [Fixnum]
|
671
880
|
attr_accessor :leap_indicator, :version_number, :mode, :stratum
|
@@ -790,7 +999,15 @@ module Scriptroute
|
|
790
999
|
# this method implemented in pure ruby regardless of interpreter state.
|
791
1000
|
# @return [String]
|
792
1001
|
def to_s
|
793
|
-
|
1002
|
+
if @probe then
|
1003
|
+
if(@probe.time) then
|
1004
|
+
"%s @%5.6f -> %s +%5.6f" % [@probe.packet, @probe.time, (@response ? @response.packet : "<none>"), (rtt or "-1")]
|
1005
|
+
else
|
1006
|
+
"%s (not seen leaving) -> %s" % [@probe.packet, (@response or "<none>")]
|
1007
|
+
end
|
1008
|
+
else
|
1009
|
+
"bizarre nothingness. no probe."
|
1010
|
+
end
|
794
1011
|
end
|
795
1012
|
end
|
796
1013
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'scriptroute/commando'
|
5
|
+
|
6
|
+
$SAFE = 2
|
7
|
+
|
8
|
+
|
9
|
+
class ScriptrouteCommandoTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def test_one
|
12
|
+
|
13
|
+
c = Commando.new([ "--start-speed", 3 ],
|
14
|
+
[ CommandoVar.new( "--start-speed", "Kbps rate to start" , "$StartSpeedKbps", 20),
|
15
|
+
CommandoVar.new( "--train-length", "Filler packets in train" , "$TrainLength", 20),
|
16
|
+
CommandoVar.new( "--hop", "Specific hop to study (ttl=hop and hop-1), 0 for e2e", "$Hop", 0 ),
|
17
|
+
CommandoVar.new( [ "--verbose", "-v" ], "print gobs of messages" , "$VERBOSE", false) ],
|
18
|
+
"destination-host")
|
19
|
+
|
20
|
+
raise "failed to set dollar startspeedkbps" unless($StartSpeedKbps == 3)
|
21
|
+
raise "failed to default dollar TrainLength" unless($TrainLength == 20)
|
22
|
+
raise "failed to construct Commando c" unless c
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/test/test_scriptroute.rb
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
require 'test/unit'
|
3
3
|
require 'scriptroute'
|
4
4
|
require 'scriptroute/nameify'
|
5
|
+
require 'ipaddr'
|
5
6
|
|
6
7
|
class ScriptrouteTest < Test::Unit::TestCase
|
7
8
|
def test_abba # be first
|
8
|
-
unless Scriptroute
|
9
|
+
unless Scriptroute.is_daemon_running?
|
9
10
|
exit
|
10
11
|
end
|
11
12
|
|
@@ -21,8 +22,8 @@ class ScriptrouteTest < Test::Unit::TestCase
|
|
21
22
|
end
|
22
23
|
def test_abc_get_config
|
23
24
|
raise "no config returned" unless Scriptroute::DaemonConfig()
|
24
|
-
raise "config not a hash" unless Scriptroute::DaemonConfig.is_a?(Hash)
|
25
|
-
raise "config lacks an expected key" unless Scriptroute::DaemonConfig
|
25
|
+
raise "config not a hash" unless Scriptroute::DaemonConfig().is_a?(Hash)
|
26
|
+
raise "config lacks an expected key" unless Scriptroute::DaemonConfig().has_key?('Filter.listen_filter')
|
26
27
|
end
|
27
28
|
def test_ally
|
28
29
|
verdict = Scriptroute::Ally.new('128.8.126.92', '128.8.126.104')
|
@@ -34,6 +35,7 @@ class ScriptrouteTest < Test::Unit::TestCase
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def test_forbidden
|
38
|
+
$stderr.puts "the next line should be 'ERROR: packet administratively filtered'..."
|
37
39
|
i = Scriptroute::ICMPecho.new(0)
|
38
40
|
# an address ending with .255 seems more likely to be broadcast.
|
39
41
|
i.ip_dst = 0x805f02ff
|
@@ -87,31 +89,35 @@ class ScriptrouteTest < Test::Unit::TestCase
|
|
87
89
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
88
90
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
89
91
|
# puts "decode: " + Scriptroute::stringpacket(m)
|
90
|
-
|
92
|
+
q = Scriptroute::pkt_from_string(m)
|
91
93
|
|
92
94
|
m = Scriptroute::ICMPtstamp.new.marshal
|
93
95
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
94
96
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
95
97
|
# puts "decode: " + Scriptroute::stringpacket(m)
|
96
|
-
|
98
|
+
q = Scriptroute::pkt_from_string(m)
|
99
|
+
# garbage. puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
97
100
|
|
98
101
|
m = Scriptroute::TCP.new.marshal
|
99
102
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
100
103
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
101
104
|
# puts "decode: " + Scriptroute::stringpacket(m)
|
102
|
-
puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
105
|
+
# garbage. puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
106
|
+
q = Scriptroute::pkt_from_string(m)
|
103
107
|
|
104
108
|
m = Scriptroute::UDP.new.marshal
|
105
109
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
106
110
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
107
111
|
# puts "decode: " + Scriptroute::stringpacket(m)
|
108
|
-
puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
112
|
+
# garbage. puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
113
|
+
q = Scriptroute::pkt_from_string(m)
|
109
114
|
|
110
115
|
m = Scriptroute::UDP.new(8).marshal
|
111
116
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
112
117
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
113
|
-
puts "decode: " + Scriptroute::IPv4.creator(m).to_s
|
114
|
-
|
118
|
+
# garbage. puts "decode: " + Scriptroute::IPv4.creator(m).to_s
|
119
|
+
q = Scriptroute::pkt_from_string(m)
|
120
|
+
# garbage. puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
115
121
|
|
116
122
|
t = Scriptroute::TCP.new
|
117
123
|
opt = Scriptroute::Timestamp_option.new(1)
|
@@ -125,7 +131,8 @@ class ScriptrouteTest < Test::Unit::TestCase
|
|
125
131
|
puts m.length.to_s + " " + m.gsub(/./) { |b| "%x " % b[0].to_i }
|
126
132
|
puts m.length.to_s + " " + m.bytes.map { |b| "%x " % b }.join if has_bytes ## 1.9
|
127
133
|
# puts "decode: " + Scriptroute::stringpacket(m)
|
128
|
-
puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
134
|
+
# garbage. puts "encode: " + Scriptroute::pkt_from_string(m).to_s
|
135
|
+
q = Scriptroute::pkt_from_string(m)
|
129
136
|
|
130
137
|
#1.times { |i|
|
131
138
|
# m=t.marshal
|
@@ -148,8 +155,157 @@ class ScriptrouteTest < Test::Unit::TestCase
|
|
148
155
|
def test_nameify
|
149
156
|
puts Scriptroute.nameify("12.123.203.170")
|
150
157
|
end
|
158
|
+
|
159
|
+
# pretty much checks whether the packet gets filled in.
|
160
|
+
# manually constructed with pack, rather than via
|
161
|
+
# scriptroute/packets
|
162
|
+
def test_v6
|
163
|
+
daddr = IPAddr.new "ff02::fb" # "fd00::230:67ff:febb:7d18"
|
164
|
+
# daddr = IPAddr.new "fe80::60c:ceff:fee4:a62" # wlp
|
165
|
+
daddr = IPAddr.new "fd00::60c:ceff:fee4:a62" # wlp
|
166
|
+
saddr = IPAddr.new "::0"
|
167
|
+
# saddr = IPAddr.new "fd00::230:67ff:febb:7d18" # zar
|
168
|
+
# saddr = IPAddr.new "fe80::230:67ff:febb:7d18" # fd00::230:67ff:febb:7d18"
|
169
|
+
payload_len = 40 #
|
170
|
+
|
171
|
+
# hard coded the cksum.
|
172
|
+
icmp = [ Scriptroute::ICMP6::ICMP6_ECHO, 0, 0xc12b, 0, 1 ].pack("ccnnn") + ( "\0" * 56 )
|
173
|
+
v6packet = [ 6 << 4, 0, 0, icmp.length, Scriptroute::IPv6::IPPROTO_ICMP6,
|
174
|
+
64, saddr.hton, daddr.hton ].pack("ccnncca16a16") + icmp
|
175
|
+
|
176
|
+
raise "too short (#{v6packet.length})" unless v6packet.length >= 40
|
177
|
+
puts v6packet.length
|
178
|
+
puts v6packet.unpack("H*")
|
179
|
+
|
180
|
+
p = Scriptroute::send_train([ Struct::DelayedPacket.new(0, v6packet) ])
|
181
|
+
puts p
|
182
|
+
|
183
|
+
p[0].probe or raise "couldn't manage sending a v6 probe"
|
184
|
+
p[0].probe.packet or raise "couldn't manage sending a v6 probe packet"
|
185
|
+
p[0].probe.packet.ip6_src or raise "v6 probe packet lacks a source"
|
186
|
+
p[0].probe.packet.ip6_src != IPAddr.new("::0") or raise "v6 probe packet source seems to be unset."
|
187
|
+
p[0].probe.packet.icmp_cksum != 0 or raise "v6 probe packet 99.99% likely unset checksum."
|
188
|
+
end
|
189
|
+
|
190
|
+
# tries to build an medium level of easiness ipv6 ping.
|
191
|
+
def test_v6_lookup
|
192
|
+
daddr = nil
|
193
|
+
Resolv::DNS.open do |dns|
|
194
|
+
ress = dns.getresources "www.google.com", Resolv::DNS::Resource::IN::AAAA
|
195
|
+
ress.each { |r| daddr = r.address.to_s }
|
196
|
+
end
|
197
|
+
raise "no address" unless daddr
|
198
|
+
p = Scriptroute::ICMP6echo.new(20)
|
199
|
+
p.ip6_dst = daddr
|
200
|
+
begin
|
201
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
202
|
+
puts r
|
203
|
+
rescue Scriptroute::ScriptrouteError => e
|
204
|
+
if e.to_s =~ /unreachable/ then
|
205
|
+
puts "Expected ERROR when host supports v6 but lacks routes to the outside world."
|
206
|
+
else
|
207
|
+
puts "rescued unexpected error: " + e.to_s
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
def test_v6_localhost
|
212
|
+
p = Scriptroute::ICMP6echo.new(20)
|
213
|
+
p.ip6_dst = IPAddr.new("::1")
|
214
|
+
p.ip6_dst = "::1"
|
215
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
216
|
+
rescue Scriptroute::ScriptrouteError => e
|
217
|
+
puts "Expected ERROR because localhost is filtered."
|
218
|
+
end
|
219
|
+
def test_v6_implicit_lookup
|
220
|
+
p = Scriptroute::UDP6.new(20)
|
221
|
+
p.ip6_dst = "www.google.com"
|
222
|
+
puts p.ip6_dst
|
223
|
+
end
|
224
|
+
|
225
|
+
# I don't know whether I want to be able to send to all-routesr
|
226
|
+
def test_v6_mcast
|
227
|
+
failure = false
|
228
|
+
begin
|
229
|
+
p = Scriptroute::ICMP6echo.new(20)
|
230
|
+
p.ip6_dst = IPAddr.new("ff02::1") # all nodes.
|
231
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
232
|
+
failure = true if r[0].probe
|
233
|
+
rescue Scriptroute::ScriptrouteError => e
|
234
|
+
puts "Expected ERROR because multicast is filtered or source cannot be set."
|
235
|
+
end
|
236
|
+
begin
|
237
|
+
p = Scriptroute::ICMP6echo.new(20)
|
238
|
+
p.ip6_dst = IPAddr.new("ff02::2") # all routers.
|
239
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
240
|
+
failure = true if r[0].probe
|
241
|
+
rescue Scriptroute::ScriptrouteError => e
|
242
|
+
puts "Expected ERROR because multicast is filtered or source cannot be set."
|
243
|
+
end
|
244
|
+
failure
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_v6_onehop
|
248
|
+
daddr = nil
|
249
|
+
Resolv::DNS.open do |dns|
|
250
|
+
ress = dns.getresources "www.google.com", Resolv::DNS::Resource::IN::AAAA
|
251
|
+
ress.each { |r| daddr = r.address.to_s }
|
252
|
+
end
|
253
|
+
raise "no address" unless daddr
|
254
|
+
p = Scriptroute::ICMP6echo.new(20)
|
255
|
+
p.ip6_dst = daddr
|
256
|
+
p.ip6_hlim = 1
|
257
|
+
begin
|
258
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
259
|
+
if r[0].response then
|
260
|
+
puts "success from first hop: %s:" % r[0].response.packet.ip6_src
|
261
|
+
puts r[0].response.packet
|
262
|
+
true
|
263
|
+
else
|
264
|
+
puts "failed: expected to see a response to the one-hop ttl limited probe"
|
265
|
+
false
|
266
|
+
end
|
267
|
+
rescue Scriptroute::ScriptrouteError => e
|
268
|
+
if e.to_s =~ /unreachable/ then
|
269
|
+
puts "Expected ERROR when host supports v6 but lacks routes to the outside world."
|
270
|
+
else
|
271
|
+
puts "rescued unexpected error: " + e.to_s
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_v6_udp_tr
|
277
|
+
daddr = nil
|
278
|
+
Resolv::DNS.open do |dns|
|
279
|
+
ress = dns.getresources "www.google.com", Resolv::DNS::Resource::IN::AAAA
|
280
|
+
ress.each { |r| daddr = r.address.to_s }
|
281
|
+
end
|
282
|
+
raise "no address" unless daddr
|
283
|
+
p = Scriptroute::UDP6.new(20)
|
284
|
+
p.ip6_dst = daddr
|
285
|
+
puts p.to_s
|
286
|
+
begin
|
287
|
+
[4,5,6,20].each { |ttl|
|
288
|
+
p.ip6_hlim = ttl
|
289
|
+
r = Scriptroute::send_train([ Struct::DelayedPacket.new(0, p) ])
|
290
|
+
if r[0].response then
|
291
|
+
puts "success from hop %d: %s:" % [ ttl, r[0].response.packet.ip6_src ]
|
292
|
+
puts r[0].response.packet
|
293
|
+
true
|
294
|
+
else
|
295
|
+
puts "failed: expected to see a response to the one-hop ttl limited probe"
|
296
|
+
false
|
297
|
+
end
|
298
|
+
}
|
299
|
+
rescue Scriptroute::ScriptrouteError => e
|
300
|
+
if e.to_s =~ /unreachable/ then
|
301
|
+
puts "Expected ERROR when host supports v6 but lacks routes to the outside world."
|
302
|
+
else
|
303
|
+
puts "rescued unexpected error: " + e.to_s
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
151
307
|
end
|
152
308
|
|
153
309
|
# Local Variables:
|
154
|
-
# compile-command: "
|
310
|
+
# compile-command: "ruby -I ../lib ./test_scriptroute.rb"
|
155
311
|
# End:
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scriptroute
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Spring
|
@@ -27,11 +27,13 @@ files:
|
|
27
27
|
- bin/sr-ping-T
|
28
28
|
- bin/sr-rockettrace
|
29
29
|
- bin/sr-traceroute
|
30
|
+
- bin/sr-traceroute6
|
30
31
|
- bin/tulip
|
31
32
|
- lib/scriptroute.rb
|
32
33
|
- lib/scriptroute/ally.rb
|
33
34
|
- lib/scriptroute/commando.rb
|
34
35
|
- lib/scriptroute/fixclock
|
36
|
+
- lib/scriptroute/ipaddr4.rb
|
35
37
|
- lib/scriptroute/liveness.rb
|
36
38
|
- lib/scriptroute/nameify.rb
|
37
39
|
- lib/scriptroute/packets.rb
|
@@ -41,6 +43,7 @@ files:
|
|
41
43
|
- lib/scriptroute/tulip/queuing.rb
|
42
44
|
- lib/scriptroute/tulip/reordering.rb
|
43
45
|
- test/test_bins.rb
|
46
|
+
- test/test_commando.rb
|
44
47
|
- test/test_scriptroute.rb
|
45
48
|
homepage: http://www.scriptroute.org/
|
46
49
|
licenses:
|