scriptroute 0.4.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/sr-ally +21 -0
- data/bin/sr-liveness +11 -0
- data/bin/sr-ping-T +69 -0
- data/bin/sr-rockettrace +51 -0
- data/bin/sr-traceroute +35 -0
- data/bin/tulip +183 -0
- data/lib/scriptroute.rb +327 -0
- data/lib/scriptroute/ally.rb +449 -0
- data/lib/scriptroute/commando.rb +228 -0
- data/lib/scriptroute/fixclock +1260 -0
- data/lib/scriptroute/liveness.rb +129 -0
- data/lib/scriptroute/nameify.rb +127 -0
- data/lib/scriptroute/packets.rb +800 -0
- data/lib/scriptroute/rockettrace.rb +181 -0
- data/lib/scriptroute/tulip/helper.rb +730 -0
- data/lib/scriptroute/tulip/loss.rb +145 -0
- data/lib/scriptroute/tulip/queuing.rb +248 -0
- data/lib/scriptroute/tulip/reordering.rb +129 -0
- data/test/test_bins.rb +20 -0
- data/test/test_scriptroute.rb +155 -0
- metadata +71 -0
@@ -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:
|