scriptroute 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/sr-ally +21 -0
- data/bin/sr-liveness +11 -0
- data/bin/sr-ping-T +69 -0
- data/bin/sr-rockettrace +51 -0
- data/bin/sr-traceroute +35 -0
- data/bin/tulip +183 -0
- data/lib/scriptroute.rb +327 -0
- data/lib/scriptroute/ally.rb +449 -0
- data/lib/scriptroute/commando.rb +228 -0
- data/lib/scriptroute/fixclock +1260 -0
- data/lib/scriptroute/liveness.rb +129 -0
- data/lib/scriptroute/nameify.rb +127 -0
- data/lib/scriptroute/packets.rb +800 -0
- data/lib/scriptroute/rockettrace.rb +181 -0
- data/lib/scriptroute/tulip/helper.rb +730 -0
- data/lib/scriptroute/tulip/loss.rb +145 -0
- data/lib/scriptroute/tulip/queuing.rb +248 -0
- data/lib/scriptroute/tulip/reordering.rb +129 -0
- data/test/test_bins.rb +20 -0
- data/test/test_scriptroute.rb +155 -0
- metadata +71 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
module Scriptroute
|
2
|
+
class LivenessTest
|
3
|
+
# whether a host is responsive for measurement; try_icmp or try_tcp.
|
4
|
+
|
5
|
+
attr_reader :responsives
|
6
|
+
attr_reader :pingfiltering
|
7
|
+
attr_reader :broken # do ping, but not http.
|
8
|
+
attr_reader :unresponsives
|
9
|
+
def LivenessTest.try_icmp(destinations)
|
10
|
+
retvalResponsives = []
|
11
|
+
retvalUnresponsives = []
|
12
|
+
begin
|
13
|
+
responses = Scriptroute::send_train( destinations.map { |dst|
|
14
|
+
begin
|
15
|
+
probe = Scriptroute::ICMPecho.new
|
16
|
+
probe.ip_dst = dst
|
17
|
+
Struct::DelayedPacket.new(0.01,probe)
|
18
|
+
rescue RuntimeError => e
|
19
|
+
# happens if dns lookup doesn't work; we'll mark it dead.
|
20
|
+
# 2013-aug added to handle dns fail.
|
21
|
+
retvalUnresponsives.push(dst)
|
22
|
+
nil
|
23
|
+
rescue => e
|
24
|
+
# happens if dns lookup doesn't work; we'll mark it dead.
|
25
|
+
retvalUnresponsives.push(dst)
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
}.compact )
|
29
|
+
responses.each_with_index { |tuple,i|
|
30
|
+
if(tuple.response &&
|
31
|
+
tuple.response.packet.is_a?(Scriptroute::ICMP) &&
|
32
|
+
tuple.response.packet.icmp_type == Scriptroute::ICMP::ICMP_ECHOREPLY) then
|
33
|
+
retvalResponsives.push(destinations[i])
|
34
|
+
else
|
35
|
+
retvalUnresponsives.push(destinations[i])
|
36
|
+
end
|
37
|
+
}
|
38
|
+
rescue => e
|
39
|
+
if(e=~/packet (%d) of %d/) then
|
40
|
+
puts "LivenessTest.try_icmp(#{destinations.join(', ')}) took #{e} when
|
41
|
+
contacting %s (or maybe %s)" % [ destinations[$1.to_i], destinations[$1.to_i-1] ]
|
42
|
+
else
|
43
|
+
puts "LivenessTest.try_icmp(#{destinations.join(', ')}) took #{e}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return retvalResponsives, retvalUnresponsives;
|
47
|
+
end
|
48
|
+
|
49
|
+
def LivenessTest.try_tcp(destinations, use_syn=false)
|
50
|
+
retvalResponsives = []
|
51
|
+
retvalUnresponsives = []
|
52
|
+
if (destinations.length < 1) then
|
53
|
+
raise "you want to give a list of destinations to probe"
|
54
|
+
end
|
55
|
+
begin
|
56
|
+
responses = Scriptroute::send_train( destinations.map { |dst|
|
57
|
+
probe = Scriptroute::TCP.new(0)
|
58
|
+
probe.ip_dst = dst
|
59
|
+
probe.flag_syn = use_syn
|
60
|
+
probe.flag_ack = !use_syn
|
61
|
+
if(use_syn) then
|
62
|
+
probe.th_dport = 80
|
63
|
+
end
|
64
|
+
Struct::DelayedPacket.new(0.01,probe)
|
65
|
+
} )
|
66
|
+
responses.each_with_index { |tuple,i|
|
67
|
+
puts tuple.probe.inspect
|
68
|
+
puts (tuple.response.inspect or "no response")
|
69
|
+
# puts tuple.to_s
|
70
|
+
if(tuple.response && tuple.response.packet.is_a?(Scriptroute::TCP) &&
|
71
|
+
( (use_syn && tuple.response.packet.flag_syn) ||
|
72
|
+
(!use_syn && tuple.response.packet.flag_rst)) ) then
|
73
|
+
retvalResponsives.push(destinations[i])
|
74
|
+
else
|
75
|
+
retvalUnresponsives.push(destinations[i])
|
76
|
+
end
|
77
|
+
}
|
78
|
+
rescue => e
|
79
|
+
if(e.to_s =~ /packet (\d+) of \d+/) then
|
80
|
+
puts "LivenessTest.try_tcp(...) took #{e} (#{$1} is to #{destinations[$1.to_i]} maybe #{destinations[$1.to_i - 1]} "
|
81
|
+
else
|
82
|
+
puts "LivenessTest.try_tcp(#{destinations.join(', ')}) took #{e} #{e.backtrace[0]}"
|
83
|
+
end
|
84
|
+
return destinations, [];
|
85
|
+
end
|
86
|
+
return retvalResponsives, retvalUnresponsives;
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(destinations)
|
90
|
+
@responsives = []
|
91
|
+
@unresponsives = []
|
92
|
+
@broken = []
|
93
|
+
if(destinations.is_a?(String)) then
|
94
|
+
# promote to an array
|
95
|
+
destinations = [ destinations ]
|
96
|
+
end
|
97
|
+
if(destinations.length == 0) then
|
98
|
+
raise "Empty array passed to LivenessTest.new()"
|
99
|
+
end
|
100
|
+
icmp_responsive, icmp_unresponsive = LivenessTest.try_icmp(destinations);
|
101
|
+
@pingfiltering, @unresponsives = if(icmp_unresponsive.length > 0) then
|
102
|
+
LivenessTest.try_tcp(icmp_unresponsive, false);
|
103
|
+
else
|
104
|
+
[[], [] ]
|
105
|
+
end
|
106
|
+
@responsives, @broken = if(icmp_responsive.length > 0) then
|
107
|
+
LivenessTest.try_tcp(icmp_responsive, true);
|
108
|
+
else
|
109
|
+
[[], []]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
"Responsives: " + @responsives.join(" ") +
|
115
|
+
"\nUnresponsivees: " + @unresponsives.join(" ") +
|
116
|
+
"\nPing Filtering: " + @pingfiltering.join(" ") +
|
117
|
+
"\nBroken (ping not http): " + @broken.join(" ")
|
118
|
+
end
|
119
|
+
def summary
|
120
|
+
"%d responsive, %d unresponsive" % [ @responsives.length, @unresponsives.length ]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if __FILE__ == $0
|
126
|
+
if ARGV.length > 0 then
|
127
|
+
puts LivenessTest.new(ARGV)
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# generates a pretty version of an ip address using
|
3
|
+
# a reverse dns lookup and running the name through
|
4
|
+
# undns for decoding.
|
5
|
+
|
6
|
+
# one might modify this to consult other databases
|
7
|
+
# in the process.
|
8
|
+
|
9
|
+
require 'scriptroute'
|
10
|
+
|
11
|
+
$have_undns = begin
|
12
|
+
require 'undns'
|
13
|
+
true
|
14
|
+
rescue LoadError, SecurityError
|
15
|
+
false
|
16
|
+
end
|
17
|
+
$have_origins = begin
|
18
|
+
$have_undns and Undns.origins_init
|
19
|
+
true
|
20
|
+
rescue
|
21
|
+
false
|
22
|
+
end
|
23
|
+
$have_socket = begin
|
24
|
+
require 'socket'
|
25
|
+
true
|
26
|
+
rescue LoadError, SecurityError
|
27
|
+
$stderr.puts "couldn't load socket"
|
28
|
+
false
|
29
|
+
end
|
30
|
+
$have_resolv = if(!$have_socket or Socket.gethostbyname("127.0.0.1")[0] !~ /localhost/) then
|
31
|
+
begin
|
32
|
+
# if socket library is not there or doesn't effectively do reverse lookups (dammit)
|
33
|
+
require 'resolv'
|
34
|
+
true
|
35
|
+
rescue LoadError, SecurityError
|
36
|
+
$stderr.puts "couldn't load resolv"
|
37
|
+
false
|
38
|
+
end
|
39
|
+
else
|
40
|
+
false # don't need it.
|
41
|
+
end
|
42
|
+
|
43
|
+
module Scriptroute
|
44
|
+
def Scriptroute::nameify(addr)
|
45
|
+
unless addr.is_a?(Scriptroute::IPaddress) then
|
46
|
+
# if not a known IP address, validate.
|
47
|
+
if addr == nil or addr == '' then
|
48
|
+
# empty string. usually a mistake somewhere.
|
49
|
+
$stderr.puts "nameify('') called from:\n " + Kernel.caller.join("\n ")
|
50
|
+
return addr
|
51
|
+
end
|
52
|
+
if(! /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(addr)) then
|
53
|
+
# not an ip address looking thing.
|
54
|
+
$stderr.puts "nameify(#{addr}) called from:\n " + Kernel.caller.join("\n ")
|
55
|
+
return addr
|
56
|
+
end
|
57
|
+
end
|
58
|
+
name =
|
59
|
+
if($have_resolv) then
|
60
|
+
begin
|
61
|
+
Resolv.getnames(addr.to_s)[0]
|
62
|
+
rescue ArgumentError => boom
|
63
|
+
# seems to happen for RFC1918 space, but probably just for those
|
64
|
+
# addresses without a name.
|
65
|
+
$stderr.puts "argument error resolving '#{addr}' to name using resolv: " + boom
|
66
|
+
nil
|
67
|
+
rescue SocketError => boom
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
elsif($have_socket) then
|
71
|
+
begin
|
72
|
+
name = (Socket.gethostbyname(addr.to_s)[0]).gsub(/\"/,'')
|
73
|
+
name
|
74
|
+
rescue ArgumentError => boom
|
75
|
+
# seems to happen for RFC1918 space, but probably just for those
|
76
|
+
# addresses without a name.
|
77
|
+
$stderr.puts "argument error resolving '#{addr}' to name: " + boom
|
78
|
+
nil
|
79
|
+
rescue SocketError => boom
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
else
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
attributes = if($have_undns) then
|
86
|
+
if(name) then
|
87
|
+
asn = Undns.get_asn_bydns(name)
|
88
|
+
if($have_origins) then
|
89
|
+
" [%d/%d]" % [ asn, Undns.origin_for_address_str(addr) ]
|
90
|
+
elsif(asn > 0) then
|
91
|
+
" [%d]" % [ asn ]
|
92
|
+
else
|
93
|
+
""
|
94
|
+
end +
|
95
|
+
if(asn > 0) then
|
96
|
+
" {%s}" % [ Undns.get_loc(asn, name) ]
|
97
|
+
else
|
98
|
+
""
|
99
|
+
end
|
100
|
+
elsif($have_origins) then
|
101
|
+
# don't have a name.
|
102
|
+
" [/%d]" % [ Undns.origin_for_address_str(addr) ]
|
103
|
+
else
|
104
|
+
""
|
105
|
+
end
|
106
|
+
else
|
107
|
+
""
|
108
|
+
end
|
109
|
+
|
110
|
+
if(name) then
|
111
|
+
name + ' (' + addr.to_s + ')' + attributes
|
112
|
+
else
|
113
|
+
addr.to_s + attributes
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
if $0 == __FILE__ then
|
120
|
+
# Undns.want_debug=1
|
121
|
+
# Undns.want_conventions_debug=1 seems to fail to work for some reason.
|
122
|
+
puts nameify("12.123.203.170")
|
123
|
+
puts Undns.get_loc(7018, "tbr1-p012502.st6wa.ip.att.net")
|
124
|
+
puts Undns.get_loc(0, "tbr1-p012502.st6wa.ip.att.net")
|
125
|
+
puts Undns.get_asn_bydns("tbr1-p012502.st6wa.ip.att.net")
|
126
|
+
end
|
127
|
+
|
@@ -0,0 +1,800 @@
|
|
1
|
+
module Scriptroute
|
2
|
+
# a small library of routines for constructing packets
|
3
|
+
# as strings to hand off to the interpreter.
|
4
|
+
|
5
|
+
class IPv4
|
6
|
+
IPPROTO_ICMP = 1
|
7
|
+
IPPROTO_TCP = 6
|
8
|
+
IPPROTO_UDP = 17
|
9
|
+
end
|
10
|
+
|
11
|
+
class ICMP < IPv4
|
12
|
+
ICMP_ECHO = 8
|
13
|
+
ICMP_ECHOREPLY = 0
|
14
|
+
ICMP_UNREACH = 3
|
15
|
+
ICMP_TIMXCEED = 11
|
16
|
+
ICMP_TIMESTAMP = 13
|
17
|
+
ICMP_TIMESTAMPREPLY = 14
|
18
|
+
ICMP_MASKREQ = 17 # /* address mask request */
|
19
|
+
ICMP_MASKREPLY = 18 # /* address mask reply */
|
20
|
+
ICMP_PARAMETERPROB = 12
|
21
|
+
|
22
|
+
ICMP_UNREACH_NET = 0 # /* bad net */
|
23
|
+
ICMP_UNREACH_HOST = 1 # /* bad host */
|
24
|
+
ICMP_UNREACH_PROTOCOL = 2 # /* bad protocol */
|
25
|
+
ICMP_UNREACH_PORT = 3 # /* bad port */
|
26
|
+
ICMP_UNREACH_NEEDFRAG = 4 # /* IP_DF caused drop */
|
27
|
+
ICMP_UNREACH_SRCFAIL = 5 # /* src route failed */
|
28
|
+
ICMP_UNREACH_NET_UNKNOWN = 6 # /* unknown net */
|
29
|
+
ICMP_UNREACH_HOST_UNKNOWN = 7 # /* unknown host */
|
30
|
+
ICMP_UNREACH_ISOLATED = 8 # /* src host isolated */
|
31
|
+
ICMP_UNREACH_NET_PROHIB = 9 # /* net denied */
|
32
|
+
ICMP_UNREACH_HOST_PROHIB = 10 # /* host denied */
|
33
|
+
ICMP_UNREACH_TOSNET = 11 # /* bad tos for net */
|
34
|
+
ICMP_UNREACH_TOSHOST = 12 # /* bad tos for host */
|
35
|
+
ICMP_UNREACH_FILTER_PROHIB = 13 # /* admin prohib */
|
36
|
+
ICMP_UNREACH_HOST_PRECEDENCE = 14 # /* host prec vio. */
|
37
|
+
ICMP_UNREACH_PRECEDENCE_CUTOFF = 15 # /* prec cutoff */
|
38
|
+
end
|
39
|
+
|
40
|
+
class IPv4option
|
41
|
+
IPOPT_EOOL = 0
|
42
|
+
IPOPT_EOL = 0 # deprecated, backward compatibility.
|
43
|
+
IPOPT_NOOP = 1
|
44
|
+
IPOPT_TS = 68
|
45
|
+
IPOPT_TIMESTAMP = IPOPT_TS
|
46
|
+
IPOPT_RR = 7
|
47
|
+
|
48
|
+
IPOPT_TS_TSONLY = 0
|
49
|
+
IPOPT_TS_TSANDADDR = 1
|
50
|
+
IPOPT_TS_PRESPEC = 3
|
51
|
+
end
|
52
|
+
|
53
|
+
class TCP < IPv4
|
54
|
+
TH_FIN = 0x01
|
55
|
+
TH_SYN = 0x02
|
56
|
+
TH_RST = 0x04
|
57
|
+
TH_PUSH = 0x08
|
58
|
+
TH_ACK = 0x10
|
59
|
+
TH_URG = 0x20
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class Array
|
65
|
+
def inject(n)
|
66
|
+
each { |value| n = yield(n, value) }
|
67
|
+
n
|
68
|
+
end
|
69
|
+
# used for calculating header length.
|
70
|
+
def sum
|
71
|
+
inject(0) { |n, value| n + value }
|
72
|
+
end
|
73
|
+
def max
|
74
|
+
inject(0) { |n, value| ((n > value) ? n : value) }
|
75
|
+
end
|
76
|
+
def even_subscripts
|
77
|
+
r = Array.new
|
78
|
+
0.step(self.length, 2) { |i| r.push(self[i]) }
|
79
|
+
r
|
80
|
+
end
|
81
|
+
def odd_subscripts
|
82
|
+
r = Array.new
|
83
|
+
1.step(self.length, 2) { |i| r.push(self[i]) }
|
84
|
+
r
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Scriptroute
|
89
|
+
class IPaddress
|
90
|
+
# @param n [String,IPaddress,Integer] a hostname, an IP address in string form, another IPaddress or an IP address in numeric form
|
91
|
+
def initialize(n)
|
92
|
+
raise "Seems bad to initialize an IPaddress with nil." unless n
|
93
|
+
if(n.is_a?(String)) then
|
94
|
+
shf = 32
|
95
|
+
if n =~ /^[\d\.]+$/ then
|
96
|
+
@addr = n.split('.').map { |i| shf -= 8; i.to_i << shf }.sum
|
97
|
+
else
|
98
|
+
addr = Scriptroute::dns_lookup(n)
|
99
|
+
if addr then
|
100
|
+
@addr = addr.split('.').map { |i| shf -= 8; i.to_i << shf }.sum
|
101
|
+
else
|
102
|
+
$stderr.puts "unable to lookup #{n}"
|
103
|
+
@addr = 0
|
104
|
+
end
|
105
|
+
end
|
106
|
+
elsif(n.is_a?(IPaddress)) then
|
107
|
+
@addr = n.to_i
|
108
|
+
else
|
109
|
+
@addr = n
|
110
|
+
end
|
111
|
+
end
|
112
|
+
# @return [String] dotted quad notation of the IP address.
|
113
|
+
def to_s
|
114
|
+
[ @addr >> 24, @addr >> 16, @addr >> 8, @addr ].map { |i| i & 0xff }.join(".")
|
115
|
+
end
|
116
|
+
# just invokes to_s for now.
|
117
|
+
# @return [String]
|
118
|
+
def inspect
|
119
|
+
to_s
|
120
|
+
end
|
121
|
+
# @return [Integer]
|
122
|
+
def to_i
|
123
|
+
@addr
|
124
|
+
end
|
125
|
+
# in case you would like to store a {Hash} with IPaddresses as keys
|
126
|
+
def hash
|
127
|
+
@addr
|
128
|
+
end
|
129
|
+
# in case you would like to store a {Hash} with IPaddresses as keys
|
130
|
+
def eql?(other)
|
131
|
+
@addr == other.to_i
|
132
|
+
end
|
133
|
+
# Compares numerically, for sorting.
|
134
|
+
def <=>(other)
|
135
|
+
@addr <=> other.to_i
|
136
|
+
end
|
137
|
+
# Compares numerically, for equality test.
|
138
|
+
def ==(other)
|
139
|
+
@addr == other.to_i
|
140
|
+
end
|
141
|
+
# @returns [String] using {Scriptroute::nameify}
|
142
|
+
def nameify
|
143
|
+
Scriptroute.nameify(self.to_s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class IPv4
|
148
|
+
# @return [Fixnum]
|
149
|
+
attr_reader :ip_hl, :ip_v, :ip_tos, :ip_len, :ip_id, :ip_off,
|
150
|
+
:ip_ttl, :ip_p, :ip_sum
|
151
|
+
# @return [IPaddress]
|
152
|
+
attr_reader :ip_src, :ip_dst
|
153
|
+
# @return [Array<IPv4option>]
|
154
|
+
attr_reader :ip_options
|
155
|
+
# @return [Fixnum]
|
156
|
+
attr_writer :ip_tos, :ip_id, :ip_off,
|
157
|
+
:ip_ttl, :ip_sum # :ip_dst,
|
158
|
+
# @return [Array<IPv4option>]
|
159
|
+
attr_writer :ip_options
|
160
|
+
|
161
|
+
# @param [String,IPaddress] destination_address the destination of this packet.
|
162
|
+
def ip_dst=(destination_address)
|
163
|
+
@ip_dst = IPaddress.new(destination_address)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [String] The packet in string form
|
167
|
+
def marshal
|
168
|
+
calculate_packet_len
|
169
|
+
[ (@ip_v << 4) + @ip_hl, @ip_tos, @ip_len,
|
170
|
+
@ip_id, @ip_off,
|
171
|
+
@ip_ttl, @ip_p, @ip_sum,
|
172
|
+
@ip_src.to_i,
|
173
|
+
@ip_dst.to_i ].pack("ccn" + "nn" + "ccn" + "N" + "N") +
|
174
|
+
@ip_options.map { |o| o.marshal }.join
|
175
|
+
end
|
176
|
+
# @return [Fixnum] the length of the payload, intended to be implemented by subclasses.
|
177
|
+
# @raise [RuntimeError] if invoked directly instead of being overloaded.
|
178
|
+
def ip_payload_len
|
179
|
+
raise "ip_payload_len is a pure virtual function in IPv4"
|
180
|
+
end
|
181
|
+
# sets ip_hl, should not be necessary to call this explicitly.
|
182
|
+
# @return [void]
|
183
|
+
def calculate_header_len
|
184
|
+
@ip_hl = [ 5 + ((ip_options.map { |o| o.ipt_len }.sum)/4.0).ceil, 15 ].min # at most 15
|
185
|
+
end
|
186
|
+
# @return [void]
|
187
|
+
def calculate_packet_len
|
188
|
+
calculate_header_len # ensures @ip_hl is set properly
|
189
|
+
raise "ip_payload_len not calculated" unless ip_payload_len
|
190
|
+
@ip_len = ip_payload_len + (@ip_hl * 4)
|
191
|
+
end
|
192
|
+
# @param opt [IPv4option] add a record route or timestamp option.
|
193
|
+
# @return [void]
|
194
|
+
def add_option(opt)
|
195
|
+
opt.is_a?(IPv4option) or raise "can add only IPv4options"
|
196
|
+
@ip_options.push(opt)
|
197
|
+
calculate_header_len
|
198
|
+
end
|
199
|
+
# This method is probably only useful for calling from subclasses.
|
200
|
+
# @param p [Fixnum] the protocol, either Scriptroute::IPv4::IPPROTO_ICMP, IPPROTO_TCP, or IPPROTO_UDP
|
201
|
+
def initialize(p)
|
202
|
+
if(p.is_a?(Fixnum)) then
|
203
|
+
@ip_v = 4
|
204
|
+
@ip_tos = 0
|
205
|
+
@ip_id = 11
|
206
|
+
@ip_off = 0
|
207
|
+
@ip_ttl = 64
|
208
|
+
@ip_p = p
|
209
|
+
@ip_sum = 0
|
210
|
+
@ip_src = 0
|
211
|
+
@ip_dst = 0
|
212
|
+
@ip_options = Array.new
|
213
|
+
calculate_packet_len
|
214
|
+
else
|
215
|
+
raise "need a protocol number to instantiate an ipv4 packet"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# a by-ip_p table of packet instantiators.
|
220
|
+
@@creators = Hash.new
|
221
|
+
|
222
|
+
# invoke a factory(?) to create a TCP/UDP/ICMP as appropriate, then
|
223
|
+
# unmarshal the IPv4 header contents.
|
224
|
+
# @param [String] str the bytes of the packet to unpack.
|
225
|
+
def IPv4.creator(str)
|
226
|
+
ip_vhl, ip_tos, ip_len,
|
227
|
+
ip_id, ip_off,
|
228
|
+
ip_ttl, ip_p, ip_sum,
|
229
|
+
ip_src, ip_dst = str.unpack("ccn" + "nn" + "ccn" + "N" + "N");
|
230
|
+
ip_hl = ip_vhl & 0xf;
|
231
|
+
if(@@creators[ip_p]) then
|
232
|
+
pkt = (@@creators[ip_p]).call(str[(ip_hl * 4) .. ip_len])
|
233
|
+
|
234
|
+
pkt.ipv4_unmarshal(str)
|
235
|
+
pkt
|
236
|
+
else
|
237
|
+
raise "unknown protocol #%d in %s" % [ ip_p, str.unpack("C*").map { |c| "%x" % c }.join(' ') ]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# unpack the ipv4 component of the packet, even if we're
|
242
|
+
# unmarshaling an instance of a subclass.
|
243
|
+
# @param [String] str The string from which to unpack.
|
244
|
+
# @return [void]
|
245
|
+
def ipv4_unmarshal(str)
|
246
|
+
ip_vhl, @ip_tos, @ip_len,
|
247
|
+
@ip_id, @ip_off,
|
248
|
+
@ip_ttl, @ip_p, @ip_sum,
|
249
|
+
ip_src, ip_dst = str.unpack("CCn" + "nn" + "CCn" + "N" + "N");
|
250
|
+
@ip_src, @ip_dst = [ip_src, ip_dst].map { |addr| IPaddress.new(addr) }
|
251
|
+
@ip_hl = ip_vhl & 0xf;
|
252
|
+
@ip_v = (ip_vhl & 0xf0) >> 4;
|
253
|
+
@ip_options = Array.new
|
254
|
+
if(@ip_hl > 5) then
|
255
|
+
add_option(IPv4option.creator(str[20 .. (@ip_hl*4)]))
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [String]
|
260
|
+
def to_s
|
261
|
+
"%s > %s ttl%d" % [ @ip_src, @ip_dst, @ip_ttl ] +
|
262
|
+
@ip_options.map { |o| o.to_s }.join(", ")
|
263
|
+
end
|
264
|
+
private :initialize
|
265
|
+
private :marshal
|
266
|
+
end
|
267
|
+
|
268
|
+
# base class for IPv4 options.
|
269
|
+
class IPv4option
|
270
|
+
# @return [Fixnum]
|
271
|
+
attr_reader :ipt_code, :ipt_len, :ipt_ptr
|
272
|
+
attr_writer :ipt_ptr
|
273
|
+
@@creators = Hash.new
|
274
|
+
def IPv4option.creator(str)
|
275
|
+
ipt_code, ipt_len, ipt_ptr = str.unpack("CCC")
|
276
|
+
if(@@creators[ipt_code]) then
|
277
|
+
pkt = (@@creators[ipt_code]).call(str)
|
278
|
+
else
|
279
|
+
raise "unknown ip option code %d" % ipt_code
|
280
|
+
end
|
281
|
+
end
|
282
|
+
def initialize(*rest)
|
283
|
+
if(rest.length == 3) then
|
284
|
+
@ipt_code = rest[0]
|
285
|
+
@ipt_len = rest[1]
|
286
|
+
@ipt_ptr = rest[2]
|
287
|
+
else
|
288
|
+
@ipt_code, @ipt_len, @ipt_ptr = rest[0].unpack("CCC")
|
289
|
+
end
|
290
|
+
end
|
291
|
+
# @return [String] The packet in string form
|
292
|
+
def marshal
|
293
|
+
# doesn't end on a word.
|
294
|
+
[ @ipt_code, @ipt_len, @ipt_ptr ].pack("CCC")
|
295
|
+
end
|
296
|
+
# @return [String]
|
297
|
+
def to_s
|
298
|
+
": opt: code %d len %d ptr %d" % [ @ipt_code, @ipt_len, @ipt_ptr ]
|
299
|
+
end
|
300
|
+
# must be instatiated through a derived class.
|
301
|
+
private :marshal
|
302
|
+
end
|
303
|
+
|
304
|
+
# The IPv4 EOOL option, which has a purpose.
|
305
|
+
class EndOfOptions_option < IPv4option
|
306
|
+
@@creators[IPOPT_EOOL] = lambda { |hdr|
|
307
|
+
p = EndOfOptions_option.new(hdr)
|
308
|
+
}
|
309
|
+
def initialize(flag_or_str)
|
310
|
+
if(flag_or_str.is_a?(Fixnum)) then
|
311
|
+
super(IPOPT_EOOL, 0, 1,nil) # maximum length is 40, but tcpdump whines.
|
312
|
+
else
|
313
|
+
@ipt_code = flag_or_str.unpack("c")
|
314
|
+
@ipt_len=1
|
315
|
+
end
|
316
|
+
end
|
317
|
+
# @return [String]
|
318
|
+
def to_s
|
319
|
+
":ip_option_code=#{@ipt_code}"
|
320
|
+
end
|
321
|
+
# @return [String] the option in string form
|
322
|
+
def marshal
|
323
|
+
@ipt_code.pack("c") # oflw will be init'd to zero
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# The IPv4 NOOP option, which has a purpose.
|
328
|
+
class NOOP_option < EndOfOptions_option
|
329
|
+
# an IP option that doesn't do anything, but we parse it anyway.
|
330
|
+
@@creators[IPOPT_NOOP] = lambda { |hdr| # hack in NOOP support as well
|
331
|
+
p = NOOP_option.new(hdr)
|
332
|
+
}
|
333
|
+
end
|
334
|
+
|
335
|
+
# The IPv4 timestamp option, which can determine routers
|
336
|
+
class Timestamp_option < IPv4option
|
337
|
+
# @return [Fixnum]
|
338
|
+
attr_reader :ts_flag, :ts_overflow
|
339
|
+
# @return [Array<IPaddress>,nil]
|
340
|
+
attr_reader :routers
|
341
|
+
# @return [Array<Fixnum>]
|
342
|
+
attr_reader :times
|
343
|
+
@@creators[IPOPT_TS] = lambda { |hdr|
|
344
|
+
p = Timestamp_option.new(hdr)
|
345
|
+
}
|
346
|
+
def initialize(flag_or_str)
|
347
|
+
if(flag_or_str.is_a?(Fixnum)) then
|
348
|
+
@routers = Array.new
|
349
|
+
@times = Array.new
|
350
|
+
@ts_flag = flag_or_str
|
351
|
+
super(IPOPT_TS, 36, 5) # maximum length is 40, but tcpdump whines.
|
352
|
+
else
|
353
|
+
ipt_code, ipt_len, ipt_ptr, ipt_of_fl = flag_or_str.unpack("CCCC");
|
354
|
+
@ts_flag = ipt_of_fl & 0x0f
|
355
|
+
@ts_overflow = (ipt_of_fl & 0xf0) >> 4
|
356
|
+
@routers, @times =
|
357
|
+
case @ts_flag
|
358
|
+
when IPOPT_TS_TSONLY
|
359
|
+
[ nil, flag_or_str.unpack("xxxxN*") ]
|
360
|
+
when IPOPT_TS_TSANDADDR, IPOPT_TS_PRESPEC
|
361
|
+
all = flag_or_str.unpack("xxxxN*")
|
362
|
+
[ all.even_subscripts[0...ipt_len/8].map { |rtr| IPaddress.new(rtr) },
|
363
|
+
all.odd_subscripts[0...(ipt_len/8)] ]
|
364
|
+
else
|
365
|
+
raise "bad timestamp flag: #{@ts_flag} (code: #{ipt_code}, len: #{ipt_len}, ptr: #{ipt_ptr})"
|
366
|
+
end
|
367
|
+
super( flag_or_str )
|
368
|
+
end
|
369
|
+
end
|
370
|
+
# @return [String] the option in string form
|
371
|
+
def marshal
|
372
|
+
super + [ @ts_flag ].pack("c") + # oflw will be init'd to zero
|
373
|
+
Array.new(@ipt_len - 4, 0).pack("c*")
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class RecordRoute_option < IPv4option
|
378
|
+
attr_reader :routers
|
379
|
+
@@creators[IPOPT_RR] = lambda { |hdr|
|
380
|
+
p = RecordRoute_option.new(hdr)
|
381
|
+
}
|
382
|
+
def initialize(*rest)
|
383
|
+
if(rest.length == 0)
|
384
|
+
super(IPOPT_RR, 39, 4)
|
385
|
+
@routers = Array.new((@ipt_len - 3 + 1)/4, 0)
|
386
|
+
else
|
387
|
+
super(rest[0])
|
388
|
+
@routers = rest[0][3..@ipt_len].unpack("N*").map { |addr| IPaddress.new(addr) }
|
389
|
+
end
|
390
|
+
end
|
391
|
+
# @return [String] the option in string form
|
392
|
+
def marshal
|
393
|
+
super + @routers.pack("N*") + "\0"
|
394
|
+
end
|
395
|
+
# @return [String]
|
396
|
+
def to_s
|
397
|
+
super + ': RR: {' + @routers.join(", ") + '}'
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
class UDP < IPv4
|
402
|
+
# @return [Fixnum]
|
403
|
+
attr_reader :uh_sport, :uh_dport, :uh_ulen, :uh_sum
|
404
|
+
attr_writer :uh_dport, :uh_sum
|
405
|
+
|
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
|
+
# @return [Fixnum] the udp data plus header length, uh_ulen
|
418
|
+
def ip_payload_len
|
419
|
+
@uh_ulen
|
420
|
+
end
|
421
|
+
|
422
|
+
# Create a new UDP packet from a payload size or from contents.
|
423
|
+
# @param paylen_or_str [Integer,String] size or contents
|
424
|
+
def initialize(paylen_or_str = 0)
|
425
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
426
|
+
if( paylen_or_str < 0) then raise "payload length must be >= 0" end
|
427
|
+
@uh_ulen = paylen_or_str + 8
|
428
|
+
if(@uh_ulen > 1480) then
|
429
|
+
raise "desired packet too big"
|
430
|
+
end
|
431
|
+
@uh_sport = 32945
|
432
|
+
@uh_dport = 33434
|
433
|
+
@uh_sum = 0
|
434
|
+
super( IPPROTO_UDP )
|
435
|
+
else
|
436
|
+
@uh_sport, @uh_dport, @uh_ulen, @uh_sum = paylen_or_str.unpack("nnnn")
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# @return [String] header and payload of this UDP datagram.
|
441
|
+
def marshal
|
442
|
+
# payload = "a%d"% (@payload_len)
|
443
|
+
# puts payload
|
444
|
+
if(@uh_ulen < 8) then warn "uh_ulen should be at least 8" end
|
445
|
+
array_of_elements = [ @uh_sport, @uh_dport, @uh_ulen, @uh_sum ]
|
446
|
+
raise "a UDP header field was unset" if array_of_elements.include?(nil)
|
447
|
+
super + [ @uh_sport, @uh_dport, @uh_ulen, @uh_sum ].pack("nnnn") +
|
448
|
+
if ( self.class == UDP ) then
|
449
|
+
"\0" * ( @uh_ulen - 8 )
|
450
|
+
else
|
451
|
+
"" # the subclass will take care of it
|
452
|
+
end
|
453
|
+
|
454
|
+
end
|
455
|
+
|
456
|
+
# Used for subclasses to set the UDP header fields.
|
457
|
+
# @param str [String] the udp header to parse into
|
458
|
+
# ports, length, and checksum.
|
459
|
+
# @return [void]
|
460
|
+
def udp_unmarshal(str)
|
461
|
+
@uh_sport, @uh_dport, @uh_ulen, @uh_sum = str.unpack("nnnn")
|
462
|
+
end
|
463
|
+
|
464
|
+
# @return [String]
|
465
|
+
def to_s
|
466
|
+
super + " UDP %d > %d len %d" % [ @uh_sport, @uh_dport, @uh_ulen ]
|
467
|
+
end
|
468
|
+
|
469
|
+
end
|
470
|
+
|
471
|
+
class TCP < IPv4
|
472
|
+
attr_reader :th_sport, :th_dport, :th_sum, :th_seq, :th_ack,
|
473
|
+
:th_win, :th_flags, :th_win, :th_sum, :th_urp
|
474
|
+
# flags don't work
|
475
|
+
attr_reader :flag_fin, :flag_syn, :flag_rst, :flag_push,
|
476
|
+
:flag_ack, :flag_urg
|
477
|
+
attr_writer :th_dport, :th_sum, :th_seq, :th_ack,
|
478
|
+
:th_win, :th_flags, :th_win, :th_sum
|
479
|
+
# flags don't work
|
480
|
+
attr_writer :flag_fin, :flag_syn, :flag_rst, :flag_push,
|
481
|
+
:flag_ack, :flag_urg
|
482
|
+
# @return [Fixnum]
|
483
|
+
attr_reader :ip_p, :ip_payload_len
|
484
|
+
|
485
|
+
@@creators[IPPROTO_TCP] = lambda { |hdr|
|
486
|
+
TCP.new(hdr)
|
487
|
+
}
|
488
|
+
|
489
|
+
def initialize(paylen_or_str = 0)
|
490
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
491
|
+
@ip_payload_len = paylen_or_str + 20 # tcp header
|
492
|
+
@ip_p = IPPROTO_TCP
|
493
|
+
@th_dport = 80
|
494
|
+
@th_sport = 0 # should be set by the daemon
|
495
|
+
@th_seq = 0 # should be set by the user, rand likely
|
496
|
+
@th_ack = 0 # should be set by the user, rand likely
|
497
|
+
@th_flags = TH_ACK # should be left alone, but could be set.
|
498
|
+
@th_win = 5180 # linux default.
|
499
|
+
@th_urp = 0 # not supported
|
500
|
+
@th_sum = 0 # should be set by the daemon
|
501
|
+
super(IPPROTO_TCP)
|
502
|
+
else
|
503
|
+
@th_sport, @th_dport, @th_seq, @th_ack, reserved, @th_flags, @th_win, @th_sum, @th_urp = paylen_or_str.unpack("nnNNCCnnn")
|
504
|
+
end
|
505
|
+
end
|
506
|
+
# @return [String] The packet in string form
|
507
|
+
def marshal
|
508
|
+
array_of_elements = [ @th_sport, @th_dport, @th_seq, @th_ack, 0x50, @th_flags, @th_win, @th_sum, @th_urp ]
|
509
|
+
raise "a TCP header field #{array_of_elements.index(nil)} was unset" if array_of_elements.include?(nil)
|
510
|
+
super + array_of_elements.pack("nnNNCCnnn")
|
511
|
+
# TODO plus payload length
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
class ICMP < IPv4
|
516
|
+
attr_reader :icmp_type, :icmp_code, :icmp_cksum
|
517
|
+
attr_reader :ip_p, :ip_payload_len
|
518
|
+
|
519
|
+
@@icmp_creators = Hash.new
|
520
|
+
@@creators[IPPROTO_ICMP] = lambda { |hdr|
|
521
|
+
icmp_type, icmp_code, icmp_cksum = hdr.unpack("CCn")
|
522
|
+
if(@@icmp_creators[icmp_type]) then
|
523
|
+
pkt = @@icmp_creators[icmp_type].call(hdr)
|
524
|
+
else
|
525
|
+
raise "unknown icmp type #%d" % icmp_type
|
526
|
+
end
|
527
|
+
}
|
528
|
+
|
529
|
+
def initialize(type_or_str)
|
530
|
+
if(type_or_str.is_a?(Fixnum)) then
|
531
|
+
@ip_p = IPPROTO_ICMP
|
532
|
+
@icmp_type = type_or_str
|
533
|
+
@icmp_code = 0
|
534
|
+
super(IPPROTO_ICMP)
|
535
|
+
else
|
536
|
+
@icmp_type, @icmp_code, @icmp_cksum = type_or_str.unpack("CCn")
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
# @return [String] The packet in string form
|
541
|
+
def marshal
|
542
|
+
@icmp_type or raise "type is nil"
|
543
|
+
@icmp_code or raise "code is nil"
|
544
|
+
@icmp_cksum = 0
|
545
|
+
super + [ @icmp_type, @icmp_code, @icmp_cksum ].pack("CCn")
|
546
|
+
end
|
547
|
+
# @return [String]
|
548
|
+
def to_s
|
549
|
+
super + ": ICMP: type %d code %d cksum %d" %[ @icmp_type, @icmp_code, @icmp_cksum ]
|
550
|
+
end
|
551
|
+
#instantiate echo or tstamp instead.
|
552
|
+
private :marshal
|
553
|
+
end
|
554
|
+
|
555
|
+
# Class for ping packets (echo and echo reply)
|
556
|
+
class ICMPecho < ICMP
|
557
|
+
# @return [Fixnum]
|
558
|
+
attr_reader :icmp_id, :icmp_seq
|
559
|
+
attr_writer :icmp_seq
|
560
|
+
@@icmp_creators[ICMP_ECHO] =
|
561
|
+
@@icmp_creators[ICMP_ECHOREPLY] = lambda { |hdr|
|
562
|
+
ICMPecho.new(hdr)
|
563
|
+
}
|
564
|
+
def initialize(paylen_or_str = 0)
|
565
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
566
|
+
if( paylen_or_str < 0) then raise "payload length must be >= 0" end
|
567
|
+
@ip_payload_len = paylen_or_str + 4 + 4
|
568
|
+
@icmp_id = 666
|
569
|
+
@icmp_seq = 1
|
570
|
+
super(ICMP_ECHO)
|
571
|
+
else
|
572
|
+
# x is skip forward a character.
|
573
|
+
@ip_payload_len = paylen_or_str.length - 8
|
574
|
+
@icmp_id, @icmp_seq = paylen_or_str.unpack("xxxxnn")
|
575
|
+
super(paylen_or_str)
|
576
|
+
end
|
577
|
+
end
|
578
|
+
# @return [String] The packet in string form
|
579
|
+
def marshal
|
580
|
+
super + [ @icmp_id, @icmp_seq ].pack("nn") + "\0" * ( @ip_payload_len - 4 - 4 )
|
581
|
+
end
|
582
|
+
# @return [String]
|
583
|
+
def to_s
|
584
|
+
super + ": ECHO: id %d seq %d len %d" % [ @icmp_id, @icmp_seq, @ip_payload_len ]
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
class ICMPtstamp < ICMP
|
589
|
+
# @return [Fixnum]
|
590
|
+
attr_reader :icmp_id, :icmp_seq
|
591
|
+
attr_reader :icmp_otime, :icmp_rtime, :icmp_ttime
|
592
|
+
attr_writer :icmp_seq
|
593
|
+
@@icmp_creators[ICMP_TIMESTAMP] =
|
594
|
+
@@icmp_creators[ICMP_TIMESTAMPREPLY] = lambda { |hdr|
|
595
|
+
ICMPtstamp.new(hdr)
|
596
|
+
}
|
597
|
+
def initialize(paylen_or_str = 0)
|
598
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
599
|
+
if( paylen_or_str < 0) then raise "payload length must be >= 0" end
|
600
|
+
@ip_payload_len = paylen_or_str + 4 + 16
|
601
|
+
@icmp_id = 666
|
602
|
+
@icmp_seq = 1
|
603
|
+
@icmp_otime = 0 # one of these three should probably be nonzero by the client, but I forget which.
|
604
|
+
@icmp_rtime = 0
|
605
|
+
@icmp_ttime = 0
|
606
|
+
super(ICMP_TIMESTAMP)
|
607
|
+
else
|
608
|
+
# x is skip forward a character.
|
609
|
+
@ip_payload_len = paylen_or_str.length - 20
|
610
|
+
@icmp_id, @icmp_seq, @icmp_otime, @icmp_rtime, @icmp_ttime = paylen_or_str.unpack("xxxxnnNNN")
|
611
|
+
super(paylen_or_str)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
# @return [String] The packet in string form
|
615
|
+
def marshal
|
616
|
+
array_of_elements = [ @icmp_id, @icmp_seq, @icmp_otime, @icmp_rtime, @icmp_ttime ]
|
617
|
+
raise "an ICMP timestamp field #{array_of_elements.index(nil)} was unset" if array_of_elements.include?(nil)
|
618
|
+
super + array_of_elements.pack("nnNNN")
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
# mask request doesn't appear to be useful.
|
623
|
+
class ICMPmaskreq < ICMP
|
624
|
+
# @return [Fixnum]
|
625
|
+
attr_reader :icmp_id, :icmp_seq
|
626
|
+
attr_writer :icmp_seq
|
627
|
+
def initialize(payload_len = 0)
|
628
|
+
if( payload_len < 0) then raise "payload length must be 0" end
|
629
|
+
if( payload_len > 0) then raise "payload length must be 0" end
|
630
|
+
@ip_payload_len = payload_len + 12
|
631
|
+
@icmp_id = 678
|
632
|
+
@icmp_seq = 1
|
633
|
+
super(ICMP_MASKREQ)
|
634
|
+
end
|
635
|
+
# @return [String] The packet in string form
|
636
|
+
def marshal
|
637
|
+
super + [ @icmp_id, @icmp_seq, @icmp_otime, @icmp_rtime, @icmp_ttime ].pack("nnNNN")
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
# also handles time exceeded messages for now
|
642
|
+
# (same format, different code )
|
643
|
+
class ICMPunreach< ICMP
|
644
|
+
# @return [IPv4] The packet header embedded within the ICMP unreachable error message.
|
645
|
+
attr_reader :contents
|
646
|
+
@@icmp_creators[ICMP_UNREACH] = @@icmp_creators[ICMP_TIMXCEED] =
|
647
|
+
lambda { |hdr|
|
648
|
+
ICMPunreach.new(hdr)
|
649
|
+
}
|
650
|
+
# Can create an unreachable only from string contents, never from filling in fields given a size.
|
651
|
+
# param string [String] the contents of the received packet.
|
652
|
+
def initialize(string)
|
653
|
+
# first four are code, type, checksum.
|
654
|
+
# second four are undefined
|
655
|
+
@contents = IPv4.creator(string[8..-1])
|
656
|
+
super(string)
|
657
|
+
end
|
658
|
+
# Cannot marshal an unreachable packet for transmission; raises an exception.
|
659
|
+
# @return [void]
|
660
|
+
def marshal
|
661
|
+
raise "not supported"
|
662
|
+
end
|
663
|
+
# @return [String] formats the packet and the embedded packet as a string.
|
664
|
+
def to_s
|
665
|
+
super + " ( " + @contents.to_s + " )"
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
class NTP < UDP
|
670
|
+
# @return [Fixnum]
|
671
|
+
attr_accessor :leap_indicator, :version_number, :mode, :stratum
|
672
|
+
attr_accessor :poll_interval, :precision, :root_delay
|
673
|
+
attr_accessor :root_dispersion, :reference_identifier
|
674
|
+
attr_accessor :reference_timestamp, :originate_timestamp
|
675
|
+
attr_accessor :receive_timestamp, :transmit_timestamp
|
676
|
+
|
677
|
+
def initialize(paylen_or_str = 0)
|
678
|
+
if(paylen_or_str.is_a?(Fixnum)) then
|
679
|
+
if( paylen_or_str < 0) then raise "payload length must be >= 0" end
|
680
|
+
|
681
|
+
@leap_indicator = 3 # alarm condition / clock not synchronized
|
682
|
+
@version_number = 4 # unclear if it should be.
|
683
|
+
@mode = 3 # client
|
684
|
+
@stratum = 0 # unspecified
|
685
|
+
@poll_interval = 4 # 16s
|
686
|
+
@precision = -6 # emulate ntpdate, though -20 is more likely
|
687
|
+
@root_delay = 1.0 # packed funny.
|
688
|
+
@root_dispersion = 1.0 # packed funny.
|
689
|
+
@reference_identifier = '0.0.0.0'
|
690
|
+
@reference_timestamp = 0.0
|
691
|
+
@originate_timestamp = 0.0
|
692
|
+
@receive_timestamp = 0.0
|
693
|
+
@transmit_timestamp = 0.0
|
694
|
+
|
695
|
+
super( paylen_or_str + 48 )
|
696
|
+
|
697
|
+
@uh_dport = 123
|
698
|
+
|
699
|
+
else
|
700
|
+
ntp_unmarshal(paylen_or_str)
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
def ntp_unmarshal(str)
|
705
|
+
|
706
|
+
ntp_lvm, @stratum, @poll_interval, @precision,
|
707
|
+
root_delay1,root_delay2,
|
708
|
+
root_dispersion1,root_dispersion2,
|
709
|
+
r1,r2,r3,r4,
|
710
|
+
reference_timestamp1,reference_timestamp2,
|
711
|
+
originate_timestamp1,originate_timestamp2,
|
712
|
+
receive_timestamp1,receive_timestamp2,
|
713
|
+
transmit_timestamp1, transmit_timestamp2 = str.unpack( "cccc" +
|
714
|
+
"nn" +
|
715
|
+
"nn" +
|
716
|
+
"cccc" +
|
717
|
+
"NN" +
|
718
|
+
"NN" +
|
719
|
+
"NN" +
|
720
|
+
"NN");
|
721
|
+
|
722
|
+
@leap_indicator = (ntp_lvm >> 6) & 0xfc
|
723
|
+
@version_number = (ntp_lvm & 0x38) >> 3
|
724
|
+
@mode = ntp_lvm & 0x07
|
725
|
+
@root_delay = root_delay1+root_delay2/65536.0
|
726
|
+
@root_dispersion = root_dispersion1+root_dispersion2/65536.0
|
727
|
+
@reference_identifier = "%d.%d.%d.%d"% [r1,r2,r3,r4].map{ |i| (i<0)?i+256:i }
|
728
|
+
@reference_timestamp = reference_timestamp1 + 0.0 + reference_timestamp2/4294967296.0
|
729
|
+
@originate_timestamp = originate_timestamp1 + originate_timestamp2/4294967296.0
|
730
|
+
@receive_timestamp = receive_timestamp1 + receive_timestamp2/4294967296.0
|
731
|
+
@transmit_timestamp = transmit_timestamp1 + transmit_timestamp2/4294967296.0
|
732
|
+
|
733
|
+
end
|
734
|
+
|
735
|
+
def float_to_two_shorts(flt)
|
736
|
+
flt == nil and raise "need a float"
|
737
|
+
[ flt.to_i, ((flt - flt.to_i) * 65536).to_i ]
|
738
|
+
end
|
739
|
+
|
740
|
+
def float_to_two_longs(flt)
|
741
|
+
flt == nil and raise "need a float"
|
742
|
+
[ flt.to_i, ((flt - flt.to_i) * 4294967296).to_i ]
|
743
|
+
end
|
744
|
+
|
745
|
+
def to_bits(int, bits)
|
746
|
+
ret = ""
|
747
|
+
(1..bits).each do |b|
|
748
|
+
ret += (int % 2).to_s
|
749
|
+
int = int / 2
|
750
|
+
end
|
751
|
+
ret
|
752
|
+
end
|
753
|
+
|
754
|
+
|
755
|
+
# @return [String] The packet in string form
|
756
|
+
def marshal
|
757
|
+
if ($VERBOSE) then
|
758
|
+
puts "marshaling with IP payload length %d" % ip_payload_len
|
759
|
+
end
|
760
|
+
super + [ @leap_indicator * 64 + @version_number * 8 + @mode, @stratum, @poll_interval, @precision,
|
761
|
+
float_to_two_shorts(@root_delay),
|
762
|
+
float_to_two_shorts(@root_dispersion),
|
763
|
+
@reference_identifier.to_i,
|
764
|
+
float_to_two_longs(@reference_timestamp),
|
765
|
+
float_to_two_longs(@originate_timestamp),
|
766
|
+
float_to_two_longs(@receive_timestamp),
|
767
|
+
float_to_two_longs(@transmit_timestamp) ].flatten.pack("cccc" +
|
768
|
+
"nn" +
|
769
|
+
"nn" +
|
770
|
+
"N" +
|
771
|
+
"NN" +
|
772
|
+
"NN" +
|
773
|
+
"NN" +
|
774
|
+
"NN") + "\0" * ( @uh_ulen - 8 - 48 )
|
775
|
+
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
# somewhat more friendly to interpret the type of the message first.
|
780
|
+
class ICMPparamter_problem < ICMP
|
781
|
+
# @return [Fixnum]
|
782
|
+
attr_reader :icmp_id, :icmp_seq
|
783
|
+
attr_writer :icmp_seq
|
784
|
+
@@icmp_creators[ICMP_PARAMETERPROB] = lambda { |hdr|
|
785
|
+
raise "ICMP_PARAMETERPROB unsupported."
|
786
|
+
}
|
787
|
+
end
|
788
|
+
|
789
|
+
class ProbeResponse
|
790
|
+
# this method implemented in pure ruby regardless of interpreter state.
|
791
|
+
# @return [String]
|
792
|
+
def to_s
|
793
|
+
"%s @%5.6f -> %s +%5.6f" % [@probe, @probe.time, (@response or "<none>"), (rtt or "-1")]
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
# Local Variables:
|
799
|
+
# compile-command: "rake test"
|
800
|
+
# End:
|