drbdump 1.0

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