rbbcc 0.11.4 → 0.11.5
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 +4 -4
- data/.gitignore +2 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -1
- data/examples/ssl_http_trace.rb +274 -0
- data/lib/rbbcc/bcc.rb +4 -1
- data/lib/rbbcc/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4757d8938ba54c8aaab6d26a1d8cf824c325b8591130d1cf65ee13c0e411e99f
|
|
4
|
+
data.tar.gz: e41edc20c58336f5aa3e1670f2f001a9cdc829291a8bd63ab53d309790b81f5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43cb1ba2fa891fc6fd970b5a48b45c28d9fff967e25ef02ed5cb7b09ddc96a91dc0e4740dbc59b9bc78f312613b76f6b7f68de9ee41bf860caddf1fb0a948c15
|
|
7
|
+
data.tar.gz: e6e8321874553ba7df4f0529477d6bea3a35f4956b704bd32bfeb409d7f43a1a59f4f9484619891e1351ed278eee42c4edda7a17d176c3b911e4c5a1dee5716a
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rbbcc (0.11.
|
|
4
|
+
rbbcc (0.11.5)
|
|
5
5
|
fiddle
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -9,6 +9,7 @@ GEM
|
|
|
9
9
|
specs:
|
|
10
10
|
coderay (1.1.3)
|
|
11
11
|
fiddle (1.1.8)
|
|
12
|
+
http-2 (1.1.3)
|
|
12
13
|
io-console (0.8.2)
|
|
13
14
|
method_source (1.1.0)
|
|
14
15
|
minitest (5.27.0)
|
|
@@ -28,6 +29,7 @@ PLATFORMS
|
|
|
28
29
|
|
|
29
30
|
DEPENDENCIES
|
|
30
31
|
bundler (~> 2.0)
|
|
32
|
+
http-2 (~> 1.1)
|
|
31
33
|
minitest (~> 5)
|
|
32
34
|
pry (~> 0.12)
|
|
33
35
|
rake (~> 13.0)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "rbbcc"
|
|
5
|
+
include RbBCC
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "http/2"
|
|
9
|
+
rescue LoadError
|
|
10
|
+
# Fallback for local vendor source
|
|
11
|
+
local_http2_lib = File.expand_path("../.agent/sources/http-2/lib", __dir__)
|
|
12
|
+
$LOAD_PATH.unshift(local_http2_lib) unless $LOAD_PATH.include?(local_http2_lib)
|
|
13
|
+
require "http/2"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
FRAME_TYPE_NAMES = {
|
|
17
|
+
0x0 => "DATA",
|
|
18
|
+
0x1 => "HEADERS",
|
|
19
|
+
0x2 => "PRIORITY",
|
|
20
|
+
0x3 => "RST_STREAM",
|
|
21
|
+
0x4 => "SETTINGS",
|
|
22
|
+
0x5 => "PUSH_PROMISE",
|
|
23
|
+
0x6 => "PING",
|
|
24
|
+
0x7 => "GOAWAY",
|
|
25
|
+
0x8 => "WINDOW_UPDATE",
|
|
26
|
+
0x9 => "CONTINUATION"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".b
|
|
30
|
+
HTTP1_METHODS = %w[GET POST PUT PATCH DELETE HEAD OPTIONS TRACE CONNECT].freeze
|
|
31
|
+
|
|
32
|
+
BPF_TEXT = <<~BPF
|
|
33
|
+
#include <uapi/linux/ptrace.h>
|
|
34
|
+
|
|
35
|
+
#define MAX_PAYLOAD_SIZE 1024
|
|
36
|
+
|
|
37
|
+
struct event_t {
|
|
38
|
+
u32 pid;
|
|
39
|
+
char event_name[8];
|
|
40
|
+
unsigned char payload[MAX_PAYLOAD_SIZE];
|
|
41
|
+
u32 data_len;
|
|
42
|
+
s32 read_ret;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
BPF_RINGBUF_OUTPUT(events, 256);
|
|
46
|
+
|
|
47
|
+
int trace_ssl_write(struct pt_regs *ctx) {
|
|
48
|
+
u32 tgid = bpf_get_current_pid_tgid() >> 32;
|
|
49
|
+
|
|
50
|
+
const char *user_buf = (const char *)PT_REGS_PARM2(ctx);
|
|
51
|
+
u64 num = (u64)PT_REGS_PARM3(ctx);
|
|
52
|
+
if (!user_buf || num <= 0) return 0;
|
|
53
|
+
|
|
54
|
+
struct event_t *event = events.ringbuf_reserve(sizeof(struct event_t));
|
|
55
|
+
if (!event) return 0;
|
|
56
|
+
|
|
57
|
+
event->pid = tgid;
|
|
58
|
+
__builtin_memcpy(event->event_name, "ssl_w", 6);
|
|
59
|
+
|
|
60
|
+
u32 len = num > MAX_PAYLOAD_SIZE ? MAX_PAYLOAD_SIZE : (u32)num;
|
|
61
|
+
event->data_len = len;
|
|
62
|
+
|
|
63
|
+
event->read_ret = bpf_probe_read_user(event->payload, len, user_buf);
|
|
64
|
+
if (event->read_ret < 0) {
|
|
65
|
+
event->data_len = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
events.ringbuf_submit(event, 0);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
BPF
|
|
72
|
+
|
|
73
|
+
def hex_ascii_dump(payload, width: 16)
|
|
74
|
+
lines = []
|
|
75
|
+
payload.bytes.each_slice(width).with_index do |slice, idx|
|
|
76
|
+
offset = idx * width
|
|
77
|
+
hex = slice.map { |b| "%02x" % b }.join(" ")
|
|
78
|
+
ascii = slice.map { |b| b >= 32 && b <= 126 ? b.chr : "." }.join
|
|
79
|
+
lines << format("%04x %-#{width * 3 - 1}s %s", offset, hex, ascii)
|
|
80
|
+
end
|
|
81
|
+
lines.join("\n")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def payload_from_event(event)
|
|
85
|
+
raw = event.payload
|
|
86
|
+
bytes = case raw
|
|
87
|
+
when String
|
|
88
|
+
raw.b
|
|
89
|
+
when Array
|
|
90
|
+
raw.map { |v| v & 0xff }.pack("C*")
|
|
91
|
+
else
|
|
92
|
+
if raw.respond_to?(:to_a)
|
|
93
|
+
raw.to_a.map { |v| v & 0xff }.pack("C*")
|
|
94
|
+
else
|
|
95
|
+
raw.to_s.b
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
len = [event.data_len.to_i, bytes.bytesize].min
|
|
100
|
+
bytes.byteslice(0, len) || "".b
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_http2_frames(payload)
|
|
104
|
+
frames = []
|
|
105
|
+
i = 0
|
|
106
|
+
total = payload.bytesize
|
|
107
|
+
|
|
108
|
+
while i + 9 <= total
|
|
109
|
+
length_bytes = payload.byteslice(i, 3).bytes
|
|
110
|
+
length = (length_bytes[0] << 16) | (length_bytes[1] << 8) | length_bytes[2]
|
|
111
|
+
frame_type = payload.getbyte(i + 3)
|
|
112
|
+
flags = payload.getbyte(i + 4)
|
|
113
|
+
stream_id = payload.byteslice(i + 5, 4).unpack1("N") & 0x7fff_ffff
|
|
114
|
+
i += 9
|
|
115
|
+
break if i + length > total
|
|
116
|
+
|
|
117
|
+
frame_payload = payload.byteslice(i, length)
|
|
118
|
+
i += length
|
|
119
|
+
frames << [frame_type, flags, stream_id, frame_payload]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
frames
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def extract_headers_fragment(frame_type, flags, frame_payload)
|
|
126
|
+
return frame_payload if frame_type == 0x9 # CONTINUATION
|
|
127
|
+
return nil unless frame_type == 0x1 # HEADERS only
|
|
128
|
+
|
|
129
|
+
start_idx = 0
|
|
130
|
+
end_idx = frame_payload.bytesize
|
|
131
|
+
|
|
132
|
+
if (flags & 0x08) != 0 # PADDED
|
|
133
|
+
return nil if end_idx.zero?
|
|
134
|
+
|
|
135
|
+
pad_len = frame_payload.getbyte(0)
|
|
136
|
+
start_idx += 1
|
|
137
|
+
end_idx = [start_idx, end_idx - pad_len].max
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if (flags & 0x20) != 0 # PRIORITY
|
|
141
|
+
return nil if start_idx + 5 > end_idx
|
|
142
|
+
|
|
143
|
+
start_idx += 5
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
frame_payload.byteslice(start_idx, end_idx - start_idx) || "".b
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def decode_pseudo_headers_with_hpack(header_block, decompressor)
|
|
150
|
+
pairs = decompressor.decode(header_block.b)
|
|
151
|
+
pseudo = {}
|
|
152
|
+
pairs.each do |k, v|
|
|
153
|
+
pseudo[k] = v
|
|
154
|
+
end
|
|
155
|
+
pseudo
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
{ ":error" => e.message }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def maybe_http1_summary(payload)
|
|
161
|
+
text = payload.force_encoding(Encoding::UTF_8)
|
|
162
|
+
first_line = text.split("\r\n", 2).first
|
|
163
|
+
return nil if first_line.nil? || first_line.empty?
|
|
164
|
+
|
|
165
|
+
if HTTP1_METHODS.any? { |m| first_line.start_with?("#{m} ") }
|
|
166
|
+
return ["request", first_line]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if first_line.start_with?("HTTP/1.1 ") || first_line.start_with?("HTTP/1.0 ")
|
|
170
|
+
return ["response", first_line]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
nil
|
|
174
|
+
rescue ArgumentError, Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def main
|
|
179
|
+
puts "SSL HTTP tracer (Ruby + ring buffer) を初期化中..."
|
|
180
|
+
|
|
181
|
+
b = BCC.new(text: BPF_TEXT)
|
|
182
|
+
|
|
183
|
+
libssl_path = ENV.fetch("LIBSSL_PATH", "/usr/lib/aarch64-linux-gnu/libssl.so.3")
|
|
184
|
+
b.attach_uprobe(name: libssl_path, sym: "SSL_write", fn_name: "trace_ssl_write")
|
|
185
|
+
puts "Attached uprobe: SSL_write (#{libssl_path})"
|
|
186
|
+
|
|
187
|
+
decompressor = HTTP2::Header::Decompressor.new
|
|
188
|
+
continuation_state = {}
|
|
189
|
+
|
|
190
|
+
b["events"].open_ring_buffer do |_ctx, data, _size|
|
|
191
|
+
event = b["events"].event(data)
|
|
192
|
+
event_name = event.event_name.to_s
|
|
193
|
+
next unless event_name == "ssl_w"
|
|
194
|
+
|
|
195
|
+
payload = payload_from_event(event)
|
|
196
|
+
puts "[PID: #{event.pid}] len=#{event.data_len} read_ret=#{event.read_ret}"
|
|
197
|
+
next if event.data_len.to_i <= 0
|
|
198
|
+
|
|
199
|
+
summary = maybe_http1_summary(payload)
|
|
200
|
+
if summary
|
|
201
|
+
kind, line = summary
|
|
202
|
+
puts "[HTTP/1.x #{kind}] #{line}"
|
|
203
|
+
puts hex_ascii_dump(payload)
|
|
204
|
+
next
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
rest = payload
|
|
208
|
+
if rest.start_with?(HTTP2_PREFACE)
|
|
209
|
+
puts "[H2] client preface detected"
|
|
210
|
+
rest = rest.byteslice(HTTP2_PREFACE.bytesize..) || "".b
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
frames = parse_http2_frames(rest)
|
|
214
|
+
if frames.empty?
|
|
215
|
+
puts hex_ascii_dump(payload)
|
|
216
|
+
next
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
frames.each do |frame_type, flags, stream_id, frame_payload|
|
|
220
|
+
frame_name = FRAME_TYPE_NAMES.fetch(frame_type, "UNKNOWN(#{frame_type})")
|
|
221
|
+
puts "[H2] type=#{frame_name} flags=0x#{format('%02x', flags)} stream=#{stream_id} len=#{frame_payload.bytesize}"
|
|
222
|
+
|
|
223
|
+
case frame_type
|
|
224
|
+
when 0x1 # HEADERS
|
|
225
|
+
fragment = extract_headers_fragment(frame_type, flags, frame_payload)
|
|
226
|
+
if fragment.nil?
|
|
227
|
+
puts "[HPACK] parse_error=invalid HEADERS payload"
|
|
228
|
+
next
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
key = [event.pid.to_i, stream_id]
|
|
232
|
+
if (flags & 0x04) != 0 # END_HEADERS
|
|
233
|
+
pseudo = decode_pseudo_headers_with_hpack(fragment, decompressor)
|
|
234
|
+
if pseudo.key?(":error")
|
|
235
|
+
puts "[HPACK] decode_error=#{pseudo[":error"]}"
|
|
236
|
+
else
|
|
237
|
+
puts "[HPACK] :method=#{pseudo.fetch(":method", "?")} :path=#{pseudo.fetch(":path", "?")}"
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
continuation_state[key] = fragment.dup
|
|
241
|
+
puts "[HPACK] collecting CONTINUATION fragments"
|
|
242
|
+
end
|
|
243
|
+
when 0x9 # CONTINUATION
|
|
244
|
+
key = [event.pid.to_i, stream_id]
|
|
245
|
+
fragment = extract_headers_fragment(frame_type, flags, frame_payload)
|
|
246
|
+
unless continuation_state.key?(key)
|
|
247
|
+
puts "[HPACK] note=orphan CONTINUATION frame"
|
|
248
|
+
next
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
continuation_state[key] << fragment
|
|
252
|
+
next if (flags & 0x04).zero?
|
|
253
|
+
|
|
254
|
+
header_block = continuation_state.delete(key)
|
|
255
|
+
pseudo = decode_pseudo_headers_with_hpack(header_block, decompressor)
|
|
256
|
+
if pseudo.key?(":error")
|
|
257
|
+
puts "[HPACK] decode_error=#{pseudo[":error"]}"
|
|
258
|
+
else
|
|
259
|
+
puts "[HPACK] :method=#{pseudo.fetch(":method", "?")} :path=#{pseudo.fetch(":path", "?")}"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
puts "監視開始。Ctrl+C で終了"
|
|
266
|
+
loop do
|
|
267
|
+
b.ring_buffer_poll(100)
|
|
268
|
+
rescue Interrupt
|
|
269
|
+
puts "\n終了します。"
|
|
270
|
+
exit(0)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
main if $PROGRAM_NAME == __FILE__
|
data/lib/rbbcc/bcc.rb
CHANGED
|
@@ -607,7 +607,10 @@ module RbBCC
|
|
|
607
607
|
end
|
|
608
608
|
|
|
609
609
|
def _open_ring_buffer(map_fd, fn, ctx)
|
|
610
|
-
|
|
610
|
+
# Avoid GC'ing function pointer
|
|
611
|
+
@_ring_buffer_fns ||= []
|
|
612
|
+
@_ring_buffer_fns << fn
|
|
613
|
+
buf = Clib.bpf_new_ringbuf(map_fd, @_ring_buffer_fns[-1], ctx)
|
|
611
614
|
if !buf
|
|
612
615
|
raise "Could not open ring buffer"
|
|
613
616
|
end
|
data/lib/rbbcc/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rbbcc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uchio Kondo
|
|
@@ -101,6 +101,7 @@ files:
|
|
|
101
101
|
- examples/py-orig/sockblock.py
|
|
102
102
|
- examples/ruby_usdt.rb
|
|
103
103
|
- examples/sbrk_trace.rb
|
|
104
|
+
- examples/ssl_http_trace.rb
|
|
104
105
|
- examples/syscalluname.rb
|
|
105
106
|
- examples/table.rb
|
|
106
107
|
- examples/tools/bashreadline.rb
|