scriptroute 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: