drbdump 1.0

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.
@@ -0,0 +1,196 @@
1
+ require 'drb'
2
+ require 'rinda/rinda'
3
+ require 'rinda/tuplespace'
4
+
5
+ ##
6
+ # A multiprocess primes generator using DRb and Rinda::TupleSpace.
7
+ #
8
+ # A distributed program using a TupleSpace has very regular message patterns,
9
+ # so it is easy to see how the program is working using drbdump. On the
10
+ # downside, this example isn't great at showing off the higher-level analysis
11
+ # of drbdump as the messages sent are of a small set with a consistent
12
+ # argument size.
13
+ #
14
+ # == Implementation Notes
15
+ #
16
+ # This program uses two TupleSpace streams and one TupleSpace counter.
17
+ #
18
+ # The first stream is the primes stream which contains the index of each found
19
+ # prime.
20
+ #
21
+ # The second stream contains checked values and is used to order insertion
22
+ # into the primes stream (so that 5 doesn't appear before 3).
23
+ #
24
+ # The counter is used to determine the next candidate value.
25
+ #
26
+ # See the book How to Write Parallel Programs: A First Course by Carriero and
27
+ # Gelernter at http://www.lindaspaces.com/book/ for a complete discussion of
28
+ # TupleSpaces.
29
+
30
+ class Primes
31
+
32
+ ##
33
+ # Setup for the process hosting the TupleSpace.
34
+
35
+ def initialize
36
+ @children = []
37
+ @tuple_space = Rinda::TupleSpace.new
38
+
39
+ DRb.start_service nil, @tuple_space
40
+
41
+ @uri = DRb.uri
42
+ end
43
+
44
+ ##
45
+ # Retrieves prime +index+ from the primes stream. This method will block if
46
+ # the given prime is being checked in another process.
47
+
48
+ def get_prime index
49
+ _, _, value = @tuple_space.read [:primes, index, nil]
50
+
51
+ value
52
+ end
53
+
54
+ ##
55
+ # Finds the next prime by dividing the next candidate value against other
56
+ # found primes
57
+
58
+ def find_prime
59
+ index = 0
60
+ candidate = next_candidate
61
+ max = Math.sqrt(candidate).ceil
62
+
63
+ prime = loop do
64
+ test = get_prime index
65
+ index += 1
66
+
67
+ break true if test >= max
68
+
69
+ _, remainder = candidate.divmod test
70
+
71
+ break false if remainder.zero?
72
+ end
73
+
74
+ mark_checked candidate, prime
75
+ end
76
+
77
+ ##
78
+ # Forks a worker child
79
+
80
+ def fork_child
81
+ Thread.start do
82
+ pid = fork do
83
+ DRb.stop_service
84
+
85
+ DRb.start_service
86
+
87
+ processor
88
+ end
89
+
90
+ Process.wait pid
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Determines the next index where a value can be added to the +stream+.
96
+
97
+ def head_index stream
98
+ head = :"#{stream}_head"
99
+ _, index = @tuple_space.take [head, nil]
100
+
101
+ index
102
+ ensure
103
+ @tuple_space.write [head, index + 1]
104
+ end
105
+
106
+ ##
107
+ # Marks +value+ as checked. If the value is +prime+ it will be added as a
108
+ # prime in the proper spot.
109
+
110
+ def mark_checked value, prime
111
+ checked_index = head_index :checked
112
+
113
+ @last_checked.upto checked_index do
114
+ @tuple_space.read [:checked, nil, nil]
115
+ end
116
+
117
+ @last_checked = checked_index
118
+
119
+ if prime then
120
+ primes_index = head_index :primes
121
+
122
+ @tuple_space.write [:primes, primes_index, value]
123
+ end
124
+
125
+ @tuple_space.write [:checked, checked_index, value]
126
+ end
127
+
128
+ ##
129
+ # Retrieves the next candidate value to work on.
130
+
131
+ def next_candidate
132
+ _, candidate = @tuple_space.take [:next_candidate, nil]
133
+
134
+ candidate
135
+ ensure
136
+ @tuple_space.write [:next_candidate, candidate + 1] if candidate
137
+ end
138
+
139
+ ##
140
+ # Initializes a prime-finding child.
141
+
142
+ def processor
143
+ @last_checked = 0
144
+ @tuple_space = Rinda::TupleSpaceProxy.new DRb::DRbObject.new_with_uri @uri
145
+
146
+ loop do
147
+ find_prime
148
+ end
149
+ end
150
+
151
+ ##
152
+ # Runs +children+ prime-finding children and displays found primes.
153
+
154
+ def run children
155
+ seed
156
+
157
+ children.times do
158
+ @children << fork_child
159
+ end
160
+
161
+ show_primes
162
+ end
163
+
164
+ ##
165
+ # Seeds the TupleSpace with base values necessary for creating the working
166
+ # streams and candidate counter.
167
+
168
+ def seed
169
+ @tuple_space.write [:primes, 0, 2]
170
+ @tuple_space.write [:primes_head, 1]
171
+
172
+ @tuple_space.write [:next_candidate, 3]
173
+
174
+ @tuple_space.write [:checked, 0, 2]
175
+ @tuple_space.write [:checked_head, 1]
176
+ end
177
+
178
+ ##
179
+ # Displays calculated primes.
180
+
181
+ def show_primes
182
+ observer = @tuple_space.notify 'write', [:primes, nil, nil]
183
+
184
+ observer.each do |_, (_, _, prime)|
185
+ puts prime
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ if $0 == __FILE__ then
192
+ children = (ARGV.shift || 2).to_i
193
+
194
+ Primes.new.run children
195
+ end
196
+
@@ -0,0 +1,714 @@
1
+ # coding: BINARY
2
+ require 'capp'
3
+ require 'drb'
4
+ require 'optparse'
5
+ require 'resolv'
6
+ require 'rinda/ring'
7
+ require 'stringio'
8
+ require 'thread'
9
+
10
+ ##
11
+ # drbdump is a tcpdump-like tool for the dRuby protocol.
12
+ #
13
+ # == Usage
14
+ #
15
+ # The +drbdump+ command-line utility works similarly to tcpdump. This is the
16
+ # easiest way to get started:
17
+ #
18
+ # sudo drbdump
19
+ #
20
+ # This captures DRb messages on your loopback and public interface. You can
21
+ # disable name resolution with <code>-n</code>. You can also drop root
22
+ # privileges with the <code>-Z</code> option if you don't want drbdump to run
23
+ # as root after it creates the capture device.
24
+ #
25
+ # == Output
26
+ #
27
+ # +drbdump+ reassembles TCP streams to create a complete message-send or
28
+ # message-result and displays it to you when complete. Here is an object in a
29
+ # Rinda::TupleSpace being renewed (checked if it is still alive), but broken
30
+ # into two lines:
31
+ #
32
+ # 17:46:27.818412 "druby://kault.local:65172" ⇒
33
+ # ("druby://kault.local:63874", 70093484759080).renew()
34
+ # 17:46:27.818709 "druby://kault.local:65172" ⇐
35
+ # "druby://kault.local:63874" success: 180
36
+ #
37
+ # The first two lines are the message-send. The first field is the timestamp
38
+ # of the packet. The second is the DRb peer the messages was sent from.
39
+ # The rightward arrow indicates this is a message-send. The remainder is
40
+ # the DRb peer and object reference (7009...) the message is being sent-to
41
+ # along with the message (+renew+). If any arguments were present they would
42
+ # appear in the argument list.
43
+ #
44
+ # The URIs are quoted to make it easy to copy part of the message into irb if
45
+ # you want to perform further debugging. For example, you can attach to the
46
+ # peer sending the message with:
47
+ #
48
+ # >> sender = DRb::DRbObject.new_with_uri "druby://kault.local:65172"
49
+ #
50
+ # You can re-send the message by copying the message from the first
51
+ # open parenthesis to the end of the line:
52
+ #
53
+ # >> DRb::DRbObject.new_with("druby://kault.local:63874", 70093484759080).
54
+ # renew()
55
+ #
56
+ # For the second two lines are the return value from the message-send. Here
57
+ # they are again:
58
+ #
59
+ # 17:46:27.818709 "druby://kault.local:65172" ⇐
60
+ # "druby://kault.local:63874" success: 180
61
+ #
62
+ # The fields are the timestamp, the DRb peer that sent the message and is
63
+ # receiving the result, the DRb peer that received the message, "success" for
64
+ # a non-exception result and the response value.
65
+ #
66
+ # Unlike +tcpdump+ drbdump always shows the peer that send the message on the
67
+ # left and uses the arrow to indicate the direction of the message.
68
+ #
69
+ # Note that the message-send and its result may be separated by other messages
70
+ # and results, so you will need to check the port values to connect a message
71
+ # send to its result.
72
+ #
73
+ # == Statistics
74
+ #
75
+ # To run drbdump in a to only display statistical information, run:
76
+ #
77
+ # drbdump -n -q -c 10000
78
+ #
79
+ # This disables name resolution and per-message output, collects 10,000
80
+ # messages then prints statistics at exit. Depending on the diversity of
81
+ # messages in your application you may need to capture a different amount of
82
+ # packets.
83
+ #
84
+ # On supporting operating systems (OS X, BSD) you can send a SIGINFO
85
+ # (control-t) to display current statistics for the basic counters at any
86
+ # time:
87
+ #
88
+ # load: 0.91 cmd: ruby 31579 running 2.48u 8.64s
89
+ # 29664 total packets captured
90
+ # 71 Rinda packets received
91
+ # 892 DRb packets received
92
+ # 446 messages sent
93
+ # 446 results received
94
+ # 0 exceptions raised
95
+ #
96
+ # These statistics are also printed when you quit drbdump.
97
+ #
98
+ # At exit, per-message statistics are displayed including message name, the
99
+ # number of argument count (to help distinguish between messages with the same
100
+ # name and different receivers), a statistical summary of allocations required
101
+ # to load the message send and result objects and a statistical summary of
102
+ # total latency (from first packet of the message-send to last packet of the
103
+ # message result:
104
+ #
105
+ # Messages sent min, avg, max, stddev:
106
+ # call (1 args) 12 sent; 3.0, 3.0, 3.0, 0.0 allocations;
107
+ # 0.214, 1.335, 6.754, 2.008 ms
108
+ # each (1 args) 6 sent; 5.0, 5.0, 5.0, 0.0 allocations;
109
+ # 0.744, 1.902, 4.771, 1.918 ms
110
+ # [] (1 args) 3 sent; 3.0, 3.0, 3.0, 0.0 allocations;
111
+ # 0.607, 1.663, 3.518, 1.612 ms
112
+ # []= (2 args) 3 sent; 5.0, 5.0, 5.0, 0.0 allocations;
113
+ # 0.737, 0.791, 0.839, 0.051 ms
114
+ # add (1 args) 2 sent; 3.0, 3.0, 3.0, 0.0 allocations;
115
+ # 0.609, 0.651, 0.694, 0.060 ms
116
+ # update (1 args) 2 sent; 3.0, 3.0, 3.0, 0.0 allocations;
117
+ # 0.246, 0.272, 0.298, 0.037 ms
118
+ # add_observer (1 args) 1 sent; 5.0, 5.0, 5.0, 0.0 allocations;
119
+ # 1.689, 1.689, 1.689, 0.000 ms
120
+ # respond_to? (2 args) 1 sent; 4.0, 4.0, 4.0, 0.0 allocations;
121
+ # 0.597, 0.597, 0.597, 0.000 ms
122
+ #
123
+ # (The above has been line-wrapped, display output is one line per.)
124
+ #
125
+ # This helps you determine which message-sends are causing more network
126
+ # traffic or are less performant overall. Some message-sends may be naturally
127
+ # long running (such as an enumerator that performs many message-sends to
128
+ # invoke its block) so a high result latency may not be indicative of a
129
+ # poorly-performing method.
130
+ #
131
+ # Messages with higher numbers of allocations typically take longer to send
132
+ # and load and create more pressure on the garbage collector. You can change
133
+ # locations that call these messages to use DRb::DRbObject references to help
134
+ # reduce the size of the messages sent.
135
+ #
136
+ # Switching entirely to sending references may increase latency as the remote
137
+ # end needs to continually ask the sender to invoke methods on its behalf.
138
+ #
139
+ # To help determine if changes you make are causing too many messages drbdump
140
+ # shows the number of messages sent between peers along with the message
141
+ # latency:
142
+ #
143
+ # Peers min, avg, max, stddev:
144
+ # 6 messages from "druby://a.example:54167" to "druby://a.example:54157"
145
+ # 0.609, 1.485, 4.771, 1.621 ms
146
+ # 4 messages from "druby://a.example:54166" to "druby://a.example:54163"
147
+ # 1.095, 2.848, 6.754, 2.645 ms
148
+ # 3 messages from "druby://a.example:54162" to "druby://a.example:54159"
149
+ # 0.246, 0.380, 0.597, 0.189 ms
150
+ # 3 messages from "druby://a.example:54169" to "druby://a.example:54163"
151
+ # 0.214, 0.254, 0.278, 0.035 ms
152
+ # 2 messages from "druby://a.example:54168" to "druby://a.example:54163"
153
+ # 0.324, 0.366, 0.407, 0.059 ms
154
+ # 2 messages from "druby://a.example:54164" to "druby://a.example:54154"
155
+ # 0.607, 0.735, 0.863, 0.181 ms
156
+ # 2 messages from "druby://a.example:54160" to "druby://a.example:54154"
157
+ # 0.798, 2.158, 3.518, 1.923 ms
158
+ # 4 single-message peers 0.225, 0.668, 1.259, 0.435 ms
159
+ #
160
+ # (The above has been line-wrapped, display output is one line per.)
161
+ #
162
+ # To save terminal lines (the peers report can be long when many messages are
163
+ # captured) any single-peer results are wrapped up into a one-line
164
+ # aggregate.
165
+ #
166
+ # An efficient API between peers would send the fewest messages with the
167
+ # fewest allocations.
168
+ #
169
+ # == Replaying packet logs
170
+ #
171
+ # You can capture and record packets with tcpdump then replay the captured
172
+ # file with drbdump. To record captured packets use <code>tcpdump -w
173
+ # dump_file</code>:
174
+ #
175
+ # $ tcpdump -i lo0 -w drb.pcap [filter]
176
+ #
177
+ # To replay the capture with drbdump give the path to the dump file to
178
+ # <code>drbdump -i</code>:
179
+ #
180
+ # $ drbdump -i drb.pcap
181
+
182
+ class DRbDump
183
+
184
+ ##
185
+ # DRbDump error class
186
+
187
+ class Error < RuntimeError
188
+ end
189
+
190
+ ##
191
+ # The version of DRbDump you are using
192
+
193
+ VERSION = '1.0'
194
+
195
+ FIN_OR_RST = Capp::TCP_FIN | Capp::TCP_RST # :nodoc:
196
+
197
+ TIMESTAMP_FORMAT = '%H:%M:%S.%6N' # :nodoc:
198
+
199
+ ##
200
+ # Number of messages to process before stopping
201
+
202
+ attr_accessor :count
203
+
204
+ ##
205
+ # Tracks if TCP packets contain DRb content or not
206
+
207
+ attr_reader :drb_streams # :nodoc:
208
+
209
+ ##
210
+ # Queue of all incoming packets from Capp.
211
+
212
+ attr_reader :incoming_packets # :nodoc:
213
+
214
+ ##
215
+ # Storage for incomplete DRb messages
216
+
217
+ attr_reader :incomplete_streams # :nodoc:
218
+
219
+ ##
220
+ # The timestamp for the first packet added to an incomplete stream
221
+
222
+ attr_reader :incomplete_timestamps # :nodoc:
223
+
224
+ ##
225
+ # The DRb protocol loader
226
+
227
+ attr_reader :loader # :nodoc:
228
+
229
+ ##
230
+ # A Resolv-compatible DNS resolver for looking up host names
231
+
232
+ attr_accessor :resolver
233
+
234
+ ##
235
+ # If true no per-packet information will be shown
236
+
237
+ attr_accessor :quiet
238
+
239
+ ##
240
+ # Directory to chroot to after starting packet capture devices (which
241
+ # require root privileges)
242
+ #
243
+ # Note that you will need to either set up a custom resolver that excludes
244
+ # Resolv::Hosts or provide /etc/hosts in the chroot directory when setting
245
+ # the run_as_directory.
246
+
247
+ attr_accessor :run_as_directory
248
+
249
+ ##
250
+ # User to run as after starting packet capture devices (which require root
251
+ # privileges)
252
+
253
+ attr_accessor :run_as_user
254
+
255
+ ##
256
+ # Collects statistics on packets and messages. See DRbDump::Statistics.
257
+
258
+ attr_reader :statistics
259
+
260
+ ##
261
+ # Converts command-line arguments +argv+ into an options Hash
262
+
263
+ def self.process_args argv
264
+ options = {
265
+ count: Float::INFINITY,
266
+ devices: [],
267
+ quiet: false,
268
+ resolve_names: true,
269
+ run_as_directory: nil,
270
+ run_as_user: nil,
271
+ }
272
+
273
+ op = OptionParser.new do |opt|
274
+ opt.program_name = File.basename $0
275
+ opt.version = VERSION
276
+ opt.release = nil
277
+ opt.banner = <<-BANNER
278
+ Usage: #{opt.program_name} [options]
279
+
280
+ drbdump dumps DRb traffic from your local network.
281
+
282
+ drbdump understands TCP traffic and Rinda broadcast queries.
283
+
284
+ For information on drbdump output and usage see `ri DRbDump`.
285
+ BANNER
286
+
287
+ opt.separator nil
288
+
289
+ opt.on('-c', '--count MESSAGES', Integer,
290
+ 'Capture the given number of message sends',
291
+ 'and exit, printing statistics.',
292
+ "\n",
293
+ 'Use with -q to analyze a sample of traffic') do |count|
294
+ options[:count] = count
295
+ end
296
+
297
+ opt.separator nil
298
+
299
+ opt.on('-i', '--interface INTERFACE',
300
+ 'The interface to listen on or a tcpdump',
301
+ 'packet capture file. Multiple interfaces',
302
+ 'can be specified.',
303
+ "\n",
304
+ 'The tcpdump default interface and the',
305
+ 'loopback interface are the drbdump',
306
+ 'defaults') do |interface|
307
+ options[:devices] << interface
308
+ end
309
+
310
+ opt.separator nil
311
+
312
+ opt.on('-n', 'Disable name resolution') do |do_not_resolve_names|
313
+ options[:resolve_names] = !do_not_resolve_names
314
+ end
315
+
316
+ opt.separator nil
317
+
318
+ opt.on('-q', '--quiet',
319
+ 'Do not print per-message information.') do |quiet|
320
+ options[:quiet] = quiet
321
+ end
322
+
323
+ opt.separator nil
324
+
325
+ opt.on( '--run-as-directory DIRECTORY',
326
+ 'chroot to the given directory after',
327
+ 'starting packet capture',
328
+ "\n",
329
+ 'Note that you must disable name resolution',
330
+ 'or provide /etc/hosts in the chroot',
331
+ 'directory') do |directory|
332
+ options[:run_as_directory] = directory
333
+ end
334
+
335
+ opt.separator nil
336
+
337
+ opt.on('-Z', '--run-as-user USER',
338
+ 'Drop root privileges and run as the',
339
+ 'given user') do |user|
340
+ options[:run_as_user] = user
341
+ end
342
+ end
343
+
344
+ op.parse! argv
345
+
346
+ options
347
+ rescue OptionParser::ParseError => e
348
+ $stderr.puts op
349
+ $stderr.puts
350
+ $stderr.puts e.message
351
+
352
+ abort
353
+ end
354
+
355
+ ##
356
+ # Starts dumping DRb traffic.
357
+
358
+ def self.run argv = ARGV
359
+ options = process_args argv
360
+
361
+ new(options).run
362
+ end
363
+
364
+ ##
365
+ # Creates a new DRbDump for +options+. The following options are
366
+ # understood:
367
+ #
368
+ # :devices::
369
+ # An Array of devices to listen on. If the Array is empty then the
370
+ # default device (see Capp::default_device_name) and the loopback device
371
+ # are used.
372
+ # :resolve_names::
373
+ # When true drbdump will look up address names.
374
+ # :run_as_user::
375
+ # When set, drop privileges from root to this user after starting packet
376
+ # capture.
377
+ # :run_as_directory::
378
+ # When set, chroot() to this directory after starting packet capture.
379
+ # Only useful with :run_as_user
380
+
381
+ def initialize options
382
+ @count = options[:count] || Float::INFINITY
383
+ @drb_config = DRb::DRbServer.make_config
384
+ @incoming_packets = Queue.new
385
+ @incomplete_streams = {}
386
+ @incomplete_timestamps = {}
387
+ @loader = DRbDump::Loader.new @drb_config
388
+ @quiet = options[:quiet]
389
+ @resolver = Resolv if options[:resolve_names]
390
+ @run_as_directory = options[:run_as_directory]
391
+ @run_as_user = options[:run_as_user]
392
+
393
+ initialize_devices options[:devices]
394
+
395
+ @capps = []
396
+ @drb_streams = {}
397
+ @running = false
398
+ @statistics = DRbDump::Statistics.new
399
+ end
400
+
401
+ def initialize_devices devices # :nodoc:
402
+ @devices = devices
403
+
404
+ if @devices.empty? then
405
+ devices = Capp.devices
406
+
407
+ abort "you must run #{$0} with root permissions, try sudo" if
408
+ devices.empty?
409
+
410
+ loopback = devices.find do |device|
411
+ device.addresses.any? do |address|
412
+ %w[127.0.0.1 ::1].include? address.address
413
+ end
414
+ end
415
+
416
+ @devices = [
417
+ Capp.default_device_name,
418
+ (loopback.name rescue nil),
419
+ ].compact
420
+ end
421
+
422
+ @devices.uniq!
423
+ end
424
+
425
+ ##
426
+ # Loop that processes captured packets.
427
+
428
+ def capture_loop capp # :nodoc:
429
+ capp.loop do |packet|
430
+ enqueue_packet packet
431
+ end
432
+ end
433
+
434
+ ##
435
+ # Removes tracking data for the stream from +source+.
436
+
437
+ def close_stream source # :nodoc:
438
+ @drb_streams.delete source
439
+ @incomplete_streams.delete source
440
+ @incomplete_timestamps.delete source
441
+ end
442
+
443
+ ##
444
+ # Creates a new Capp instance that listens on +device+ for DRb and Rinda
445
+ # packets.
446
+
447
+ def create_capp device # :nodoc:
448
+ capp = Capp.open device
449
+
450
+ capp.filter = <<-FILTER
451
+ (tcp and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)) or
452
+ (tcp[tcpflags] & (tcp-fin|tcp-rst) != 0) or
453
+ (udp port #{Rinda::Ring_PORT})
454
+ FILTER
455
+
456
+ capp
457
+ end
458
+
459
+ ##
460
+ # Displays information from Rinda::RingFinger packet +packet+.
461
+ #
462
+ # Currently only understands RingFinger broadcast packets.
463
+
464
+ def display_ring_finger packet
465
+ @statistics.rinda_packet_count += 1
466
+
467
+ return if @quiet
468
+
469
+ obj = Marshal.load packet.payload
470
+
471
+ (_, tell), timeout = obj
472
+
473
+ puts '%s find ring on %s for %s timeout: %d' % [
474
+ packet.timestamp.strftime(TIMESTAMP_FORMAT),
475
+ packet.destination(@resolver), tell.__drburi,
476
+ timeout
477
+ ]
478
+ rescue
479
+ end
480
+
481
+ ##
482
+ # Displays information from the possible DRb packet +packet+
483
+
484
+ def display_drb packet
485
+ return unless @running
486
+ return unless stream = packet_stream(packet)
487
+
488
+ source = packet.source
489
+
490
+ message = DRbDump::Message.from_stream self, packet, stream
491
+
492
+ message.display
493
+
494
+ stop if @statistics.drb_messages_sent >= @count
495
+
496
+ @statistics.drb_packet_count += 1
497
+ @drb_streams[source] = true
498
+ @incomplete_timestamps.delete source
499
+ rescue DRbDump::Loader::TooLarge
500
+ display_drb_too_large packet
501
+ rescue DRbDump::Loader::Premature, DRbDump::Loader::DataError
502
+ @incomplete_streams[source] = stream.string
503
+ @incomplete_timestamps[source] ||= packet.timestamp
504
+ rescue DRbDump::Loader::Error
505
+ @drb_streams[source] = false
506
+ end
507
+
508
+ ##
509
+ # Writes the start of a DRb stream from a packet that was too large to
510
+ # transmit.
511
+
512
+ def display_drb_too_large packet # :nodoc:
513
+ return if @quiet
514
+
515
+ rest = packet.payload
516
+
517
+ source, destination = resolve_addresses packet
518
+
519
+ valid, size, rest = valid_in_payload rest
520
+
521
+ puts '%s %s to %s packet too large, valid: [%s] too big (%d bytes): %s' % [
522
+ packet.timestamp.strftime(TIMESTAMP_FORMAT),
523
+ source, destination,
524
+ valid.join(', '), size, rest.dump
525
+ ]
526
+ end
527
+
528
+ ##
529
+ # Starts a thread that displays each captured packet.
530
+
531
+ def display_packets
532
+ @running = true
533
+
534
+ @display_thread = Thread.new do
535
+ while @running and packet = @incoming_packets.deq do
536
+ if packet.udp? then
537
+ display_ring_finger packet
538
+ else
539
+ display_drb packet
540
+ end
541
+ end
542
+ end
543
+ end
544
+
545
+ ##
546
+ # Enqueues +packet+ unless it is a FIN or RST or the stream is not a DRb
547
+ # stream.
548
+
549
+ def enqueue_packet packet # :nodoc:
550
+ @statistics.total_packet_count += 1
551
+
552
+ if packet.tcp? and 0 != packet.tcp_header.flags & FIN_OR_RST then
553
+ close_stream packet.source
554
+
555
+ return
556
+ end
557
+
558
+ return if @drb_streams[packet.source] == false
559
+
560
+ @incoming_packets.enq packet
561
+ end
562
+
563
+ ##
564
+ # Loads Marshal data in +object+ if possible, or returns a DRb::DRbUnknown
565
+ # if there was some error.
566
+
567
+ def load_marshal_data object # :nodoc:
568
+ object.load
569
+ rescue NameError, ArgumentError => e
570
+ DRb::DRbUnknown.new e, object.stream
571
+ end
572
+
573
+ ##
574
+ # Returns a StringIO created from packets that are part of the TCP
575
+ # connection in +stream+.
576
+ #
577
+ # Returns nil if the stream is not a DRb message stream or the packet is
578
+ # empty.
579
+
580
+ def packet_stream packet # :nodoc:
581
+ payload = packet.payload
582
+
583
+ return if payload.empty?
584
+
585
+ source = packet.source
586
+
587
+ if previous = @incomplete_streams.delete(source) then
588
+ payload = previous << payload
589
+ elsif /\A....\x04\x08/m !~ payload then
590
+ @drb_streams[source] = false
591
+ return
592
+ end
593
+
594
+ stream = StringIO.new payload
595
+ stream.set_encoding Encoding::BINARY, Encoding::BINARY
596
+ stream
597
+ end
598
+
599
+ ##
600
+ # Resolves source and destination addresses in +packet+ for use in DRb URIs.
601
+
602
+ def resolve_addresses packet # :nodoc:
603
+ source = packet.source @resolver
604
+ source = "\"druby://#{source.sub(/\.(\d+)$/, ':\1')}\""
605
+
606
+ destination = packet.destination @resolver
607
+ destination = "\"druby://#{destination.sub(/\.(\d+)$/, ':\1')}\""
608
+
609
+ return source, destination
610
+ end
611
+
612
+ ##
613
+ # Captures packets and displays them on the screen.
614
+
615
+ def run
616
+ capps = @devices.map { |device| create_capp device }
617
+
618
+ Capp.drop_privileges @run_as_user, @run_as_directory
619
+
620
+ start_capture capps
621
+
622
+ trap_info
623
+
624
+ display_packets.join
625
+ rescue Interrupt
626
+ untrap_info
627
+
628
+ stop
629
+
630
+ @display_thread.join
631
+
632
+ puts # clear ^C
633
+
634
+ exit
635
+ ensure
636
+ @statistics.show
637
+ end
638
+
639
+ ##
640
+ # Captures DRb packets and feeds them to the incoming_packets queue
641
+
642
+ def start_capture capps
643
+ @capps.concat capps
644
+
645
+ capps.map do |capp|
646
+ Thread.new do
647
+ capture_loop capp
648
+ end
649
+ end
650
+ end
651
+
652
+ ##
653
+ # Stops the message capture and packet display. If root privileges were
654
+ # dropped message capture cannot be restarted.
655
+
656
+ def stop
657
+ @running = false
658
+
659
+ @capps.each do |capp|
660
+ capp.stop
661
+ end
662
+
663
+ @incoming_packets.enq nil
664
+ end
665
+
666
+ ##
667
+ # Adds a SIGINFO handler if the OS supports it
668
+
669
+ def trap_info
670
+ return unless Signal.list['INFO']
671
+
672
+ trap 'INFO' do
673
+ @statistics.show_basic
674
+ end
675
+ end
676
+
677
+ ##
678
+ # Sets the SIGINFO handler to the DEFAULT handler
679
+
680
+ def untrap_info
681
+ return unless Signal.list['INFO']
682
+
683
+ trap 'INFO', 'DEFAULT'
684
+ end
685
+
686
+ ##
687
+ # Returns the valid parts, the size and content of the invalid part in
688
+ # +large_packet+
689
+
690
+ def valid_in_payload too_large # :nodoc:
691
+ load_limit = @drb_config[:load_limit]
692
+
693
+ size = nil
694
+ valid = []
695
+
696
+ loop do
697
+ size, too_large = too_large.unpack 'Na*'
698
+
699
+ break if load_limit < size
700
+
701
+ valid << Marshal.load(too_large.slice!(0, size)).inspect
702
+ end
703
+
704
+ return valid, size, too_large
705
+ end
706
+
707
+ end
708
+
709
+ require 'drbdump/loader'
710
+ require 'drbdump/message'
711
+ require 'drbdump/message_send'
712
+ require 'drbdump/message_result'
713
+ require 'drbdump/statistic'
714
+ require 'drbdump/statistics'