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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +30 -0
- data/README.rdoc +71 -0
- data/Rakefile +30 -0
- data/bin/drbdump +6 -0
- data/example/ping.rb +241 -0
- data/example/service_primes.rb +212 -0
- data/example/tuplespace_primes.rb +196 -0
- data/lib/drbdump.rb +714 -0
- data/lib/drbdump/loader.rb +75 -0
- data/lib/drbdump/message.rb +72 -0
- data/lib/drbdump/message_result.rb +84 -0
- data/lib/drbdump/message_send.rb +146 -0
- data/lib/drbdump/statistic.rb +90 -0
- data/lib/drbdump/statistics.rb +318 -0
- data/lib/drbdump/test_case.rb +91 -0
- data/test/arg.dump +0 -0
- data/test/drb_fin.dump +0 -0
- data/test/http.dump +0 -0
- data/test/ping.dump +0 -0
- data/test/ring.dump +0 -0
- data/test/test_drbdump.rb +283 -0
- data/test/test_drbdump_loader.rb +88 -0
- data/test/test_drbdump_message.rb +31 -0
- data/test/test_drbdump_message_result.rb +73 -0
- data/test/test_drbdump_message_send.rb +105 -0
- data/test/test_drbdump_statistic.rb +98 -0
- data/test/test_drbdump_statistics.rb +264 -0
- data/test/too_large_packet.pcap +0 -0
- metadata +183 -0
- metadata.gz.sig +0 -0
@@ -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
|
+
|
data/lib/drbdump.rb
ADDED
@@ -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'
|