woolen_common 0.0.1
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 +15 -0
- data/README.md +31 -0
- data/ext/woolen_common/extconf.rb +26 -0
- data/ext/woolen_common/linux.h +3 -0
- data/ext/woolen_common/win.c +18 -0
- data/ext/woolen_common/win.h +4 -0
- data/ext/woolen_common/win/puts_color.c +139 -0
- data/ext/woolen_common/win/puts_color.h +5 -0
- data/ext/woolen_common/woolen_common.c +20 -0
- data/ext/woolen_common/woolen_common.h +7 -0
- data/lib/woolen_common.rb +39 -0
- data/lib/woolen_common/abstract_middleware/builder.rb +138 -0
- data/lib/woolen_common/abstract_middleware/map_cfg_manager.rb +52 -0
- data/lib/woolen_common/abstract_middleware/runner.rb +72 -0
- data/lib/woolen_common/action_pool_proxy.rb +28 -0
- data/lib/woolen_common/actionpool.rb +10 -0
- data/lib/woolen_common/actionpool/pool.rb +295 -0
- data/lib/woolen_common/actionpool/queue.rb +41 -0
- data/lib/woolen_common/actionpool/thread.rb +181 -0
- data/lib/woolen_common/addr_helper.rb +93 -0
- data/lib/woolen_common/cache.rb +305 -0
- data/lib/woolen_common/common_helper.rb +42 -0
- data/lib/woolen_common/config_manager.rb +36 -0
- data/lib/woolen_common/drb_helper.rb +125 -0
- data/lib/woolen_common/ffi/win32_kernel32.rb +86 -0
- data/lib/woolen_common/logger.rb +419 -0
- data/lib/woolen_common/pcap/mu/fixnum_ext.rb +8 -0
- data/lib/woolen_common/pcap/mu/pcap/ethernet.rb +164 -0
- data/lib/woolen_common/pcap/mu/pcap/header.rb +76 -0
- data/lib/woolen_common/pcap/mu/pcap/io_pair.rb +68 -0
- data/lib/woolen_common/pcap/mu/pcap/io_wrapper.rb +77 -0
- data/lib/woolen_common/pcap/mu/pcap/ip.rb +62 -0
- data/lib/woolen_common/pcap/mu/pcap/ipv4.rb +274 -0
- data/lib/woolen_common/pcap/mu/pcap/ipv6.rb +149 -0
- data/lib/woolen_common/pcap/mu/pcap/packet.rb +106 -0
- data/lib/woolen_common/pcap/mu/pcap/pkthdr.rb +162 -0
- data/lib/woolen_common/pcap/mu/pcap/reader.rb +62 -0
- data/lib/woolen_common/pcap/mu/pcap/reader/http_family.rb +175 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp.rb +369 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/chunk.rb +124 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/data.rb +135 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/init.rb +101 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/init_ack.rb +69 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/parameter.rb +111 -0
- data/lib/woolen_common/pcap/mu/pcap/sctp/parameter/ip_address.rb +49 -0
- data/lib/woolen_common/pcap/mu/pcap/stream_packetizer.rb +74 -0
- data/lib/woolen_common/pcap/mu/pcap/tcp.rb +522 -0
- data/lib/woolen_common/pcap/mu/pcap/udp.rb +81 -0
- data/lib/woolen_common/pcap/mu/scenario/pcap.rb +175 -0
- data/lib/woolen_common/pcap/mu/scenario/pcap/fields.rb +51 -0
- data/lib/woolen_common/pcap/mu/scenario/pcap/rtp.rb +72 -0
- data/lib/woolen_common/pcap/pcap.rb +115 -0
- data/lib/woolen_common/pcap/readme.md +72 -0
- data/lib/woolen_common/ruby_ext/blank.rb +126 -0
- data/lib/woolen_common/ruby_ext/drb_ext.rb +7 -0
- data/lib/woolen_common/ruby_ext/string.rb +116 -0
- data/lib/woolen_common/ruby_ext/win32_ole.rb +4 -0
- data/lib/woolen_common/ruby_proxy.rb +5 -0
- data/lib/woolen_common/ruby_proxy/client.rb +305 -0
- data/lib/woolen_common/ruby_proxy/config.rb +44 -0
- data/lib/woolen_common/ruby_proxy/exceptions.rb +17 -0
- data/lib/woolen_common/ruby_proxy/proxy.rb +157 -0
- data/lib/woolen_common/ruby_proxy/proxy_global_set.rb +44 -0
- data/lib/woolen_common/ruby_proxy/proxy_load.rb +34 -0
- data/lib/woolen_common/ruby_proxy/server.rb +53 -0
- data/lib/woolen_common/splib.rb +36 -0
- data/lib/woolen_common/splib/Array.rb +33 -0
- data/lib/woolen_common/splib/CodeReloader.rb +59 -0
- data/lib/woolen_common/splib/Constants.rb +44 -0
- data/lib/woolen_common/splib/Conversions.rb +47 -0
- data/lib/woolen_common/splib/Exec.rb +132 -0
- data/lib/woolen_common/splib/Float.rb +13 -0
- data/lib/woolen_common/splib/HumanIdealRandomIterator.rb +40 -0
- data/lib/woolen_common/splib/Monitor.rb +214 -0
- data/lib/woolen_common/splib/PriorityQueue.rb +110 -0
- data/lib/woolen_common/splib/Sleep.rb +10 -0
- data/lib/woolen_common/splib/UrlShorteners.rb +48 -0
- data/lib/woolen_common/ssh_proxy.rb +146 -0
- data/lib/woolen_common/system_helper.rb +123 -0
- data/lib/woolen_common/system_monitor.rb +23 -0
- data/lib/woolen_common/system_monitor/linux_monitor.rb +250 -0
- data/lib/woolen_common/system_monitor/windows_monitor.rb +145 -0
- data/lib/woolen_common/type_helper.rb +42 -0
- data/lib/woolen_common/ver_ctrl_middle_ware.rb +92 -0
- data/lib/woolen_common/version.rb +3 -0
- metadata +210 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# http://www.mudynamics.com
|
3
|
+
# http://labs.mudynamics.com
|
4
|
+
# http://www.pcapr.net
|
5
|
+
|
6
|
+
module Mu
|
7
|
+
class Pcap
|
8
|
+
class SCTP
|
9
|
+
class Parameter
|
10
|
+
|
11
|
+
class IpAddress < Parameter
|
12
|
+
attr_accessor :value
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
|
17
|
+
@value = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_bytes type, size, bytes
|
21
|
+
# Basic validation
|
22
|
+
if PARAM_IPV4 == type
|
23
|
+
Pcap.assert(size == 8, "Invalid IPv4 address: 4 != #{size}")
|
24
|
+
else
|
25
|
+
Pcap.assert(size == 20, "Invalid IPv6 address: 16 != #{size}")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create IP address parameter
|
29
|
+
ip_address = IpAddress.new
|
30
|
+
ip_address.type = type
|
31
|
+
ip_address.size = size
|
32
|
+
ip_address.value = IPAddr.new_ntoh(bytes[0, size - 4])
|
33
|
+
|
34
|
+
# Set raw payload
|
35
|
+
ip_address.payload_raw = bytes[0, size - 4]
|
36
|
+
|
37
|
+
# Return the result
|
38
|
+
return ip_address
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
return "address(%s)" % [@value]
|
43
|
+
end
|
44
|
+
end # class IpAddress
|
45
|
+
|
46
|
+
end # class Parameter
|
47
|
+
end # class SCTP
|
48
|
+
end # class Pcap
|
49
|
+
end # module Mu
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# http://www.mudynamics.com
|
3
|
+
# http://labs.mudynamics.com
|
4
|
+
# http://www.pcapr.net
|
5
|
+
|
6
|
+
require 'mu/pcap/io_pair'
|
7
|
+
require 'mu/pcap/io_wrapper'
|
8
|
+
|
9
|
+
module Mu
|
10
|
+
class Pcap
|
11
|
+
class StreamPacketizer
|
12
|
+
attr_reader :io_pair, :parser
|
13
|
+
|
14
|
+
def initialize parser
|
15
|
+
@parser = parser
|
16
|
+
@key_to_idx = Hash.new do |hash, key|
|
17
|
+
if hash.size >= 2
|
18
|
+
raise ArgumentError, "Only two endpoints are allowed in a transaction"
|
19
|
+
end
|
20
|
+
hash[key] = hash.size
|
21
|
+
end
|
22
|
+
@sent_messages = [[], []].freeze
|
23
|
+
@inner_pair = IOPair.stream_pair
|
24
|
+
@io_pair = @inner_pair.map { |io| IOWrapper.new io, parser }.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def msg_count key
|
28
|
+
key = key.inspect
|
29
|
+
widx = @key_to_idx[key]
|
30
|
+
messages = @sent_messages[widx]
|
31
|
+
messages.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def extra_bytes w_key
|
35
|
+
w_key = w_key.inspect
|
36
|
+
|
37
|
+
ridx = @key_to_idx[w_key] ^ 1
|
38
|
+
reader = @io_pair[ridx]
|
39
|
+
incomplete = reader.unread
|
40
|
+
incomplete.empty? ? nil : incomplete.dup
|
41
|
+
end
|
42
|
+
|
43
|
+
def push key, bytes
|
44
|
+
key = key.inspect
|
45
|
+
widx = @key_to_idx[key]
|
46
|
+
writer = @io_pair[widx]
|
47
|
+
raw_writer = @inner_pair[widx]
|
48
|
+
raw_writer.write bytes
|
49
|
+
|
50
|
+
messages = @sent_messages[widx]
|
51
|
+
|
52
|
+
ridx = widx ^ 1
|
53
|
+
reader = @io_pair[ridx]
|
54
|
+
while msg = reader.read
|
55
|
+
messages << msg
|
56
|
+
writer.record_write bytes
|
57
|
+
end
|
58
|
+
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def next_msg key
|
63
|
+
key = key.inspect
|
64
|
+
idx = @key_to_idx[key]
|
65
|
+
if m = @sent_messages[idx].shift
|
66
|
+
return m.dup
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,522 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# http://www.mudynamics.com
|
3
|
+
# http://labs.mudynamics.com
|
4
|
+
# http://www.pcapr.net
|
5
|
+
|
6
|
+
require 'mu/pcap/reader'
|
7
|
+
require 'mu/pcap/stream_packetizer'
|
8
|
+
|
9
|
+
module Mu
|
10
|
+
class Pcap
|
11
|
+
|
12
|
+
class TCP < Packet
|
13
|
+
attr_accessor :src_port, :dst_port, :seq, :ack, :flags, :window, :urgent, :mss, :proto_family,:tcp_options
|
14
|
+
|
15
|
+
TH_FIN = 0x01
|
16
|
+
TH_SYN = 0x02
|
17
|
+
TH_RST = 0x04
|
18
|
+
TH_PUSH = 0x08
|
19
|
+
TH_ACK = 0x10
|
20
|
+
TH_URG = 0x20
|
21
|
+
TH_ECE = 0x40
|
22
|
+
TH_CWR = 0x80
|
23
|
+
|
24
|
+
MSS = 2
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
super
|
28
|
+
@src_port = 0
|
29
|
+
@dst_port = 0
|
30
|
+
@seq = 0
|
31
|
+
@ack = 0
|
32
|
+
@flags = 0
|
33
|
+
@window = 0
|
34
|
+
@urgent = 0
|
35
|
+
@mss = 0
|
36
|
+
@proto_family = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def flow_id
|
40
|
+
return [:tcp, @src_port, @dst_port]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.from_bytes bytes
|
44
|
+
Pcap.assert bytes.length >= 20, 'Truncated TCP header: ' +
|
45
|
+
"expected 20 bytes, got #{bytes.length} bytes"
|
46
|
+
sport, dport, seq, ack, offset, flags, win, sum, urp =
|
47
|
+
bytes.unpack('nnNNCCnnn')
|
48
|
+
offset = (offset >> 4) * 4
|
49
|
+
Pcap.assert offset >= 20, 'Truncated TCP header: ' +
|
50
|
+
"expected at least 20 bytes, got #{offset} bytes"
|
51
|
+
Pcap.assert bytes.length >= offset, 'Truncated TCP header: ' +
|
52
|
+
"expected at least #{offset} bytes, got #{bytes.length} bytes"
|
53
|
+
|
54
|
+
if TH_SYN == flags
|
55
|
+
ss = TCP.get_option bytes[20, offset-20], MSS
|
56
|
+
tcp_options = bytes[20, offset-20]
|
57
|
+
else
|
58
|
+
tcp_options = ''
|
59
|
+
ss = 0
|
60
|
+
end
|
61
|
+
|
62
|
+
IPv4.check_options bytes[20, offset-20], 'TCP'
|
63
|
+
|
64
|
+
tcp = TCP.new
|
65
|
+
tcp.src_port = sport
|
66
|
+
tcp.dst_port = dport
|
67
|
+
tcp.seq = seq
|
68
|
+
tcp.ack = ack
|
69
|
+
tcp.flags = flags
|
70
|
+
tcp.window = win
|
71
|
+
tcp.urgent = urp
|
72
|
+
tcp.mss = ss
|
73
|
+
tcp.tcp_options = tcp_options
|
74
|
+
tcp.payload = tcp.payload_raw = bytes[offset..-1]
|
75
|
+
return tcp
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.get_option options, option_type
|
79
|
+
while not options.empty?
|
80
|
+
type = options.slice!(0, 1)[0].ord
|
81
|
+
if type == 0 or type == 1
|
82
|
+
next
|
83
|
+
end
|
84
|
+
length = options.slice!(0, 1)[0].ord
|
85
|
+
if 2 < length
|
86
|
+
case length
|
87
|
+
when 3
|
88
|
+
format = "C"
|
89
|
+
when 4
|
90
|
+
format = "n"
|
91
|
+
when 6
|
92
|
+
format = "N"
|
93
|
+
when 10
|
94
|
+
format = "Q"
|
95
|
+
else
|
96
|
+
Pcap.warning "Bad TCP option length: #{length}"
|
97
|
+
end
|
98
|
+
option = options.slice!(0, length - 2).unpack(format)[0]
|
99
|
+
end
|
100
|
+
if option_type == type
|
101
|
+
return option
|
102
|
+
end
|
103
|
+
end
|
104
|
+
return 0
|
105
|
+
end
|
106
|
+
|
107
|
+
def write io, ip
|
108
|
+
if @payload.bytesize + 40 > 65535
|
109
|
+
raise NotImplementedError, "TCP segment too large"
|
110
|
+
end
|
111
|
+
options = ''
|
112
|
+
options = @tcp_options if @tcp_options != nil
|
113
|
+
all_head_length = 20 + options.bytesize
|
114
|
+
pseudo_header = ip.pseudo_header(all_head_length + @payload.bytesize)
|
115
|
+
head_length_to_pack = (all_head_length / 4).to_int << 4
|
116
|
+
@src_port = @src_port.to_i if @src_port.is_a? String
|
117
|
+
@dst_port = @dst_port.to_i if @dst_port.is_a? String
|
118
|
+
@flags = @flags.to_i if @flags.is_a? String
|
119
|
+
@window = @window.to_i if @window.is_a? String
|
120
|
+
@urgent = @urgent.to_i if @urgent.is_a? String
|
121
|
+
header = [@src_port, @dst_port, @seq, @ack, head_length_to_pack, @flags, @window,
|
122
|
+
0, @urgent].pack('nnNNCCnnn')
|
123
|
+
checksum = IP.checksum pseudo_header + header + options + @payload
|
124
|
+
header = [@src_port, @dst_port, @seq, @ack, head_length_to_pack, @flags, @window,
|
125
|
+
checksum, @urgent].pack('nnNNCCnnn')
|
126
|
+
io.write header
|
127
|
+
io.write options
|
128
|
+
io.write @payload
|
129
|
+
end
|
130
|
+
|
131
|
+
class ReorderError < StandardError;
|
132
|
+
end
|
133
|
+
|
134
|
+
ReorderState = ::Struct.new(:next_seq, :queued)
|
135
|
+
|
136
|
+
# Reorder packets by TCP sequence number. TCP packets are assumed to
|
137
|
+
# be over IP over Ethernet.
|
138
|
+
def self.reorder packets
|
139
|
+
packets = packets.dup
|
140
|
+
reordered_packets = []
|
141
|
+
flow_to_state = {}
|
142
|
+
while not packets.empty?
|
143
|
+
packet = packets.shift
|
144
|
+
# Don't reorder non-TCP packets
|
145
|
+
if not tcp? packet
|
146
|
+
reordered_packets << packet
|
147
|
+
next
|
148
|
+
end
|
149
|
+
# Sanity check: must not be a fragment
|
150
|
+
if packet.payload.v4? and packet.payload.fragment?
|
151
|
+
raise ReorderError, "TCP stream contains IP fragments"
|
152
|
+
end
|
153
|
+
tcp = packet.payload.payload
|
154
|
+
# Must not contain urgent data
|
155
|
+
if tcp.flags & TH_URG != 0
|
156
|
+
raise ReorderError, "TCP stream contains urgent data: "+
|
157
|
+
pretty_flow_name(packet)
|
158
|
+
end
|
159
|
+
# Get/create state
|
160
|
+
if flow_to_state.member? packet.flow_id
|
161
|
+
state = flow_to_state[packet.flow_id]
|
162
|
+
else
|
163
|
+
state = ReorderState.new nil, []
|
164
|
+
flow_to_state[packet.flow_id] = state
|
165
|
+
end
|
166
|
+
if not state.next_seq
|
167
|
+
# First packet in TCP stream
|
168
|
+
reordered_packets << packet
|
169
|
+
state.next_seq = tcp.seq + tcp.payload.length
|
170
|
+
if tcp.flags & TCP::TH_SYN != 0
|
171
|
+
state.next_seq += 1
|
172
|
+
end
|
173
|
+
if tcp.flags & TCP::TH_FIN != 0
|
174
|
+
state.next_seq += 1
|
175
|
+
end
|
176
|
+
state.next_seq %= 2**32
|
177
|
+
elsif seq_eq(tcp.seq, state.next_seq)
|
178
|
+
# Next expected sequence number in TCP stream
|
179
|
+
|
180
|
+
# SYN must not appear in middle of stream
|
181
|
+
if tcp.flags & TCP::TH_SYN != 0
|
182
|
+
raise ReorderError, "SYN in middle of TCP stream " +
|
183
|
+
pretty_flow_name(packet)
|
184
|
+
end
|
185
|
+
|
186
|
+
reordered_packets << packet
|
187
|
+
state.next_seq += tcp.payload.length
|
188
|
+
if tcp.flags & TCP::TH_FIN != 0
|
189
|
+
state.next_seq += 1
|
190
|
+
end
|
191
|
+
state.next_seq %= 2**32
|
192
|
+
|
193
|
+
# Reinject any packets in the queue into the packet stream
|
194
|
+
if not state.queued.empty?
|
195
|
+
packets.unshift(*state.queued)
|
196
|
+
state.queued.clear
|
197
|
+
end
|
198
|
+
elsif seq_lt(tcp.seq, state.next_seq)
|
199
|
+
# Old sequence number
|
200
|
+
if seq_lte(tcp.seq + tcp.payload.length, state.next_seq)
|
201
|
+
# No overlap: retransmitted packet, ignore
|
202
|
+
else
|
203
|
+
# Overlap: reassembler must slice in overlapping data
|
204
|
+
reordered_packets << packet
|
205
|
+
end
|
206
|
+
else
|
207
|
+
# Future sequence number - queue
|
208
|
+
state.queued << packet
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
flow_to_state.each do |flow_id, state|
|
213
|
+
if not state.queued.empty?
|
214
|
+
raise ReorderError, "Data missing from TCP stream "+
|
215
|
+
pretty_flow_name(state.queued[0]) + ': ' +
|
216
|
+
"expecting sequence number #{state.next_seq}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
return reordered_packets
|
221
|
+
end
|
222
|
+
|
223
|
+
class MergeError < StandardError;
|
224
|
+
end
|
225
|
+
|
226
|
+
# Merge adjacent TCP packets. Non-data TCP packets are also removed.
|
227
|
+
# reorder() should be run first. This can create packets that are larger
|
228
|
+
# than the maximum possible IPv4 packet - use split() to make them smaller.
|
229
|
+
def self.merge packets
|
230
|
+
merged_packets = []
|
231
|
+
merged_packet = nil
|
232
|
+
next_seq = nil
|
233
|
+
packets.each do |packet|
|
234
|
+
if not tcp? packet
|
235
|
+
# Skip non-TCP packets.
|
236
|
+
if merged_packet
|
237
|
+
merged_packets << merged_packet
|
238
|
+
merged_packet = nil
|
239
|
+
end
|
240
|
+
merged_packets << packet
|
241
|
+
elsif packet.payload.v4? and packet.payload.fragment?
|
242
|
+
# Sanity check: must not be a fragment
|
243
|
+
raise MergeError, 'TCP stream contains IP fragments'
|
244
|
+
else
|
245
|
+
tcp = packet.payload.payload
|
246
|
+
if tcp.flags & TCP::TH_SYN == 0 and tcp.payload == ''
|
247
|
+
# Ignore non-data packets. SYNs are kept so the TCP
|
248
|
+
# transport is created at the correct spot.
|
249
|
+
elsif not merged_packet or
|
250
|
+
merged_packet.flow_id != packet.flow_id
|
251
|
+
# New TCP stream
|
252
|
+
if merged_packet
|
253
|
+
merged_packets << merged_packet
|
254
|
+
end
|
255
|
+
merged_packet = packet.deepdup
|
256
|
+
next_seq = tcp.seq + tcp.payload.length
|
257
|
+
elsif seq_eq tcp.seq, next_seq
|
258
|
+
# Next expected sequence number
|
259
|
+
merged_packet.payload.payload.payload << tcp.payload
|
260
|
+
next_seq += tcp.payload.length
|
261
|
+
elsif seq_lte(tcp.seq + tcp.payload.length, next_seq)
|
262
|
+
# Old data: ignore
|
263
|
+
elsif seq_lt tcp.seq, next_seq
|
264
|
+
# Overlapping segment: merge newest part
|
265
|
+
length = seq_sub(tcp.seq + tcp.payload.length, next_seq)
|
266
|
+
bytes = tcp.payload[-length..-1]
|
267
|
+
merged_packet.payload.payload.payload << bytes
|
268
|
+
next_seq += length
|
269
|
+
else
|
270
|
+
# Error (sanify check, reorder_tcp will raise an error)
|
271
|
+
raise MergeError, 'TCP stream is missing segments'
|
272
|
+
end
|
273
|
+
if next_seq
|
274
|
+
if tcp.flags & TCP::TH_SYN != 0
|
275
|
+
next_seq += 1
|
276
|
+
end
|
277
|
+
if tcp.flags & TCP::TH_FIN != 0
|
278
|
+
next_seq += 1
|
279
|
+
end
|
280
|
+
next_seq %= 2**32
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
if merged_packet
|
285
|
+
merged_packets << merged_packet
|
286
|
+
end
|
287
|
+
|
288
|
+
merged_packets = create_message_boundaries(merged_packets)
|
289
|
+
|
290
|
+
return merged_packets
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.create_message_boundaries packets
|
294
|
+
# Get complete bytes for each tcp flow before trying to
|
295
|
+
# identify the protocol.
|
296
|
+
flow_to_bytes = {}
|
297
|
+
packets.each do |packet|
|
298
|
+
if tcp? packet
|
299
|
+
tcp = packet.payload.payload
|
300
|
+
flow = packet.flow_id
|
301
|
+
bytes = flow_to_bytes[flow] ||= ""
|
302
|
+
bytes << tcp.payload.to_s
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# If any proto plugin can parse a message off of the stream we will
|
307
|
+
# use that plugin to detect message boundaries and guide message
|
308
|
+
# reassembly.
|
309
|
+
flow_to_packetizer = {}
|
310
|
+
flow_to_bytes.each_pair do |flow, bytes|
|
311
|
+
[Reader::HttpFamily].each do |klass|
|
312
|
+
reader = klass.new
|
313
|
+
reader.pcap2scenario = true
|
314
|
+
if reader.read_message bytes
|
315
|
+
tx_key = flow.flatten.sort_by { |o| o.to_s }
|
316
|
+
|
317
|
+
tx = flow_to_packetizer[tx_key] ||= StreamPacketizer.new(klass.new)
|
318
|
+
break
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Merge/split packets along message boundaries. This is done as an
|
324
|
+
# atomic transaction per tcp connection. The loop below adds merged
|
325
|
+
# packets alongside the original unmerged packets. If the stream
|
326
|
+
# is completely merged (no fragments left at end) we remove the
|
327
|
+
# original packets otherwise we rollback by removing the newly
|
328
|
+
# created packets.
|
329
|
+
changes = Hash.new do |hash, key|
|
330
|
+
# tuple of original/replacement packets per flow.
|
331
|
+
hash[key] = [[], []]
|
332
|
+
end
|
333
|
+
rollback_list = []
|
334
|
+
|
335
|
+
merged = []
|
336
|
+
partial_messages = Hash.new { |hash, key| hash[key] = [] }
|
337
|
+
packets.each do |packet|
|
338
|
+
merged << packet
|
339
|
+
|
340
|
+
next if not tcp? packet
|
341
|
+
tcp = packet.payload.payload
|
342
|
+
|
343
|
+
flow = packet.flow_id
|
344
|
+
|
345
|
+
# Check if we have message boundaries for this flow
|
346
|
+
tx_key = flow.flatten.sort_by { |o| o.to_s }
|
347
|
+
if not tx = flow_to_packetizer[tx_key]
|
348
|
+
next
|
349
|
+
end
|
350
|
+
|
351
|
+
# Keep track of new vs orig packets so we can delete one set at the end.
|
352
|
+
orig_packets, new_packets = changes[flow]
|
353
|
+
orig_packets << packet
|
354
|
+
|
355
|
+
if tcp.payload.empty?
|
356
|
+
p = packet.deepdup
|
357
|
+
new_packets << p
|
358
|
+
p.payload.payload.proto_family = tx.parser.family
|
359
|
+
next
|
360
|
+
end
|
361
|
+
|
362
|
+
# Does the current packet result in any completed messages?
|
363
|
+
tx.push(flow, tcp.payload)
|
364
|
+
fragments = partial_messages[flow]
|
365
|
+
if tx.msg_count(flow) == 0
|
366
|
+
# No, record packet as a fragment and move to next packet.
|
367
|
+
fragments << packet
|
368
|
+
next
|
369
|
+
end
|
370
|
+
|
371
|
+
# Yes, packet did result in completed messages. Create a new
|
372
|
+
# tcp packet for each higher level protocol message.
|
373
|
+
first_inc_packet = (fragments.empty? ? packet : fragments[0])
|
374
|
+
next_seq = first_inc_packet.payload.payload.seq
|
375
|
+
while tcp_payload = tx.next_msg(flow)
|
376
|
+
if tcp_payload.size > MAX_SEGMENT_PAYLOAD
|
377
|
+
# Abort merging for this flow because this packet
|
378
|
+
# will be split and result in a scenario where
|
379
|
+
# we send one logical message but try and receive
|
380
|
+
# two.
|
381
|
+
rollback_list << tx_key
|
382
|
+
$stderr.puts "Warning: Message too big, cannot enforce " \
|
383
|
+
"message boundaries."
|
384
|
+
end
|
385
|
+
next_packet = packet.deepdup
|
386
|
+
new_packets << next_packet
|
387
|
+
next_tcp = next_packet.payload.payload
|
388
|
+
next_tcp.seq = next_seq
|
389
|
+
next_tcp.payload = tcp_payload
|
390
|
+
next_tcp.proto_family = tx.parser.family
|
391
|
+
next_seq += tcp_payload.size
|
392
|
+
merged << next_packet
|
393
|
+
end
|
394
|
+
fragments.clear
|
395
|
+
|
396
|
+
# If there are unconsumed bytes then add a fragment to the
|
397
|
+
# incomplete list.
|
398
|
+
if extra_bytes = tx.extra_bytes(flow)
|
399
|
+
frag = packet.deepdup
|
400
|
+
new_packets << frag
|
401
|
+
fragments << frag
|
402
|
+
tcp = frag.payload.payload
|
403
|
+
tcp.payload = extra_bytes
|
404
|
+
tcp.seq = next_seq
|
405
|
+
tcp.proto_family = tx.parser.family
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Figure out which connections have incompletely merged flows.
|
410
|
+
# Rollback for those and commit the rest.
|
411
|
+
partial_messages.each_pair do |flow, list|
|
412
|
+
if not list.empty?
|
413
|
+
tx_key = flow.flatten.sort_by { |o| o.to_s }
|
414
|
+
$stderr.puts "Warning: Left over fragments, cannot force message boundaries."
|
415
|
+
rollback_list << tx_key
|
416
|
+
end
|
417
|
+
end
|
418
|
+
changes.each_pair do |flow, orig_new|
|
419
|
+
orig, new = orig_new
|
420
|
+
tx_key = flow.flatten.sort_by { |o| o.to_s }
|
421
|
+
if rollback_list.include?(tx_key)
|
422
|
+
new.each { |p| p.payload = :remove }
|
423
|
+
else
|
424
|
+
orig.each { |p| p.payload = :remove }
|
425
|
+
end
|
426
|
+
end
|
427
|
+
merged.reject! { |p| p.payload == :remove }
|
428
|
+
|
429
|
+
merged
|
430
|
+
end
|
431
|
+
|
432
|
+
|
433
|
+
# Split-up TCP packets that are too large to serialize. (I.e., total
|
434
|
+
# length including all headers greater than 65535 - 20 - 20 - 14.)
|
435
|
+
MAX_SEGMENT_PAYLOAD = 65535 - 20 - 20 - 14
|
436
|
+
|
437
|
+
def self.split packets
|
438
|
+
split_packets = []
|
439
|
+
packets.each do |packet|
|
440
|
+
if not tcp? packet
|
441
|
+
# Skip non-TCP packets.
|
442
|
+
split_packets << packet
|
443
|
+
next
|
444
|
+
elsif packet.payload.v4? and packet.payload.fragment?
|
445
|
+
# Sanity check: must not be a fragment
|
446
|
+
raise MergeError, 'TCP stream contains IP fragments'
|
447
|
+
elsif packet.payload.payload.payload.length <= MAX_SEGMENT_PAYLOAD
|
448
|
+
split_packets << packet
|
449
|
+
else
|
450
|
+
tcp = packet.payload.payload
|
451
|
+
payload = tcp.payload
|
452
|
+
tcp.payload = payload.slice! 0, MAX_SEGMENT_PAYLOAD
|
453
|
+
next_seq = tcp.seq + tcp.payload.length
|
454
|
+
split_packets << packet
|
455
|
+
while payload != ''
|
456
|
+
next_packet = packet.deepdup
|
457
|
+
next_tcp = next_packet.payload.payload
|
458
|
+
next_tcp.seq = next_seq
|
459
|
+
next_tcp.payload = payload.slice! 0, MAX_SEGMENT_PAYLOAD
|
460
|
+
next_seq += next_tcp.payload.length
|
461
|
+
split_packets << next_packet
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
return split_packets
|
466
|
+
end
|
467
|
+
|
468
|
+
def self.tcp? packet
|
469
|
+
return packet.is_a?(Ethernet) &&
|
470
|
+
packet.payload.is_a?(IP) &&
|
471
|
+
packet.payload.payload.is_a?(TCP)
|
472
|
+
end
|
473
|
+
|
474
|
+
# Subtract two sequence numbers module 2**32.
|
475
|
+
def self.seq_sub a, b
|
476
|
+
if a - b > 2**31
|
477
|
+
return -((b - a) % 2**32)
|
478
|
+
elsif a - b < -2**31
|
479
|
+
return (a - b) % 2**32
|
480
|
+
else
|
481
|
+
return a - b
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# Compare TCP sequence numbers modulo 2**32.
|
486
|
+
def self.seq_eq a, b
|
487
|
+
return seq_sub(a, b) == 0
|
488
|
+
end
|
489
|
+
|
490
|
+
def self.seq_lt a, b
|
491
|
+
return seq_sub(a, b) < 0
|
492
|
+
end
|
493
|
+
|
494
|
+
def self.seq_lte a, b
|
495
|
+
return seq_sub(a, b) <= 0
|
496
|
+
end
|
497
|
+
|
498
|
+
# Generate a pretty name for a TCP flow
|
499
|
+
def self.pretty_flow_name packet
|
500
|
+
ip = packet.payload
|
501
|
+
return "#{ip.src}:#{ip.payload.src_port} <-> " +
|
502
|
+
"#{ip.dst}:#{ip.payload.dst_port}"
|
503
|
+
end
|
504
|
+
|
505
|
+
def to_s
|
506
|
+
return "tcp(%d, %d, %s)" % [@src_port, @dst_port, @payload.inspect]
|
507
|
+
end
|
508
|
+
|
509
|
+
def == other
|
510
|
+
return super &&
|
511
|
+
self.src_port == other.src_port &&
|
512
|
+
self.dst_port == other.dst_port &&
|
513
|
+
self.seq == other.seq &&
|
514
|
+
self.ack == other.ack &&
|
515
|
+
self.flags == other.flags &&
|
516
|
+
self.window == other.window &&
|
517
|
+
self.urgent == other.urgent
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
end
|
522
|
+
end
|