-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathpipe_perf_test.go
More file actions
363 lines (340 loc) · 13.4 KB
/
Copy pathpipe_perf_test.go
File metadata and controls
363 lines (340 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
package warp
import (
"bytes"
"testing"
)
// newPerfPipe constructs a Pipe with a no-op afterCommHook and minimal state
// so that mediator functions can be called in isolation by benchmarks/tests.
func newPerfPipe() *Pipe {
return &Pipe{
bufferSize: 10 * 1024 * 1024,
afterCommHook: func(b Data, to Direction) {},
afterConnHook: func() {},
}
}
// buildEHLOResponseChunk returns a typical EHLO response advertising STARTTLS.
func buildEHLOResponseChunk() []byte {
return []byte(
"250-mail.example.com\r\n" +
"250-PIPELINING\r\n" +
"250-SIZE 10240000\r\n" +
"250-STARTTLS\r\n" +
"250 8BITMIME\r\n",
)
}
// buildMailBodyChunk returns a synthetic mail body chunk of the given size.
// It deliberately embeds substrings that mimic real-world false positives for
// the SMTP-response classifiers:
// - "250" (in a Received header)
// - "STARTTLS" (as a literal word in documentation-like body text)
// - the exact sequence "250-STARTTLS\r\n" that the misfired
// removeStartTLSCommand attempts to bytes.Replace
//
// The result is padded with non-matching text up to size bytes.
func buildMailBodyChunk(size int) []byte {
header := "Received: from sender.example (sender.example [192.0.2.250])\r\n" +
"\tby mx.example.com with ESMTP id 250abc; deployment notes\r\n" +
"From: alice@example.test\r\n" +
"To: bob@example.local\r\n" +
"Subject: Notes on TLS deployment\r\n" +
"\r\n" +
"Our migration plan mentions 250-STARTTLS\r\n" +
"as an EHLO continuation marker.\r\n"
var buf bytes.Buffer
buf.WriteString(header)
for buf.Len() < size {
buf.WriteString("Lorem ipsum dolor sit amet. ")
}
body := buf.Bytes()
if len(body) > size {
body = body[:size]
}
return body
}
// ─────────────────────────────────────────────────────────────────────
// Layer A: Function-level benchmarks for the per-chunk hot paths in
// Pipe.mediateOnUpstream / mediateOnDownstream. CPU and allocation
// profiles taken under sustained mail-burst load showed three offenders:
//
// 1. bytes.Split inside setTimeAtDataStarting / hasResponseCode —
// builds [][]byte and TrimRight-s each segment, on every chunk.
// 2. bytes.Contains scans across the full read buffer in the EHLO
// response classifiers, even after the first EHLO response has
// already been handled.
// 3. bytes.Replace inside removeStartTLSCommand allocating a fresh
// copy of the full chunk when a mail body happens to contain
// "250-STARTTLS\r\n" or "250 STARTTLS\r\n".
//
// Each benchmark records B/op and allocs/op via b.ReportAllocs so the
// effect of the fixes is measurable. b.SetBytes is set to the input
// chunk size so `MB/s` throughput is also reported.
// ─────────────────────────────────────────────────────────────────────
// BenchmarkHasResponseCode_LargeBuffer measures bytes.Split inside
// hasResponseCode against a large buffer.
func BenchmarkHasResponseCode_LargeBuffer(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = p.hasResponseCode(chunk, codeStartingMailInput)
}
}
// BenchmarkIsResponseOfEHLOWithStartTLS_MailBody shows the cost of the two
// bytes.Contains scans over a large buffer.
func BenchmarkIsResponseOfEHLOWithStartTLS_MailBody(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = p.isResponseOfEHLOWithStartTLS(chunk)
}
}
// BenchmarkMediateOnDownstream_LargeBuffer measures the per-chunk cost of
// mediateOnDownstream on a 1 MiB chunk. p.tls is forced to true so that the
// removeStartTLSCommand path is suppressed; this isolates the scan-only cost
// of the always-on classifiers and the inline 354 detection.
func BenchmarkMediateOnDownstream_LargeBuffer(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
p.tls.Store(true)
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnDownstream(buf, len(buf))
}
}
// BenchmarkMediateOnDownstream_LargeBuffer_WithMisfire enables the
// removeStartTLSCommand misfire path (p.tls=false) so the per-iteration
// cost includes bytes.Replace allocations over the full buffer when the
// chunk happens to contain the literal "250-STARTTLS\r\n" sequence.
//
// This benchmark numerically captures the cost of the misfire pattern
// observed in production: the EHLO-response classifier returned true on
// mail-body chunks that incidentally contained both "250" (e.g. in a
// Received-header IP fragment) and "STARTTLS" (as a literal word), and
// removeStartTLSCommand then copied the entire chunk via bytes.Replace.
// Allocation profile from one production sample attributed ~1.5 GB of
// total allocation to this single bytes.Replace call site.
func BenchmarkMediateOnDownstream_LargeBuffer_WithMisfire(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnDownstream(buf, len(buf))
}
}
// BenchmarkMediateOnUpstream_DataBody_PlainSMTP measures upstream mediation
// when the receiver mail address is already known (RCPT TO already processed):
// the regex-based extraction is skipped per the existing guard.
func BenchmarkMediateOnUpstream_DataBody_PlainSMTP(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
p.rMailAddr = []byte("bob@example.local")
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnUpstream(buf, len(buf))
}
}
// BenchmarkMediateOnUpstream_InDataPhase represents the post-fix steady state
// of upstream mediation for a non-filter connection: the proxy has observed
// the server's 354 reply downstream, set inDataPhase=true, and every
// subsequent upstream chunk is mail body bytes. The fast path bypasses all
// command/regex scans and only looks for the end-of-data terminator.
func BenchmarkMediateOnUpstream_InDataPhase(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
p.inDataPhase.Store(true)
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnUpstream(buf, len(buf))
}
}
// BenchmarkMediateOnUpstream_DataBody_NoRcptSet measures upstream mediation
// when rMailAddr has not been set yet: every chunk runs the regex-based
// sender/receiver extraction over the full buffer.
func BenchmarkMediateOnUpstream_DataBody_NoRcptSet(b *testing.B) {
const size = 1024 * 1024
chunk := buildMailBodyChunk(size)
p := newPerfPipe()
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnUpstream(buf, len(buf))
}
}
// BenchmarkMediateOnDownstream_EHLOResponse is a baseline for comparison:
// a real EHLO response is small (~100 B), so the per-chunk scan cost should
// be negligible compared to the large-buffer benchmarks above.
func BenchmarkMediateOnDownstream_EHLOResponse(b *testing.B) {
chunk := buildEHLOResponseChunk()
p := newPerfPipe()
p.tls.Store(true) // suppress STARTTLS path; we want the scan cost only
buf := make([]byte, 0, len(chunk))
b.SetBytes(int64(len(chunk)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf = append(buf[:0], chunk...)
_, _, _ = p.mediateOnDownstream(buf, len(buf))
}
}
// ─────────────────────────────────────────────────────────────────────
// Layer B: Regression test documenting the removeStartTLSCommand misfire.
// ─────────────────────────────────────────────────────────────────────
// TestMediateOnDownstream_RemoveStartTLSNoMisfireAfterEHLO verifies the
// once-only latch on the EHLO-response classifier.
//
// Background: removeStartTLSCommand exists to strip "250-STARTTLS\r\n"
// from the EHLO reply so the client never tries to upgrade. Without a
// latch the classifier fires on any downstream chunk that contains both
// "250" and "STARTTLS", including a mail body that incidentally has the
// literal "250-STARTTLS\r\n" sequence (documentation text, captured
// EHLO traces, log excerpts, etc.). Each misfire allocates a full-chunk
// bytes.Replace copy and flips p.readytls, which corrupts the
// STARTTLS state machine.
//
// After the ehloResponseHandled latch lands, once the real EHLO reply
// has been processed any subsequent downstream chunk — even one that
// looks like an EHLO reply — must NOT retrigger removeStartTLSCommand,
// must NOT allocate a full-buffer bytes.Replace copy, and must NOT
// flip p.readytls.
func TestMediateOnDownstream_RemoveStartTLSNoMisfireAfterEHLO(t *testing.T) {
p := &Pipe{
afterCommHook: func(b Data, to Direction) {},
afterConnHook: func() {},
}
// First, process a legitimate EHLO response so the ehloResponseHandled
// latch is engaged. The buffer is a complete "250-...\r\n...250 ...\r\n"
// response advertising STARTTLS — the real classifier target.
ehlo := buildEHLOResponseChunk()
if !p.isResponseOfEHLOWithStartTLS(ehlo) {
t.Fatalf("precondition: classifier should fire on a real EHLO response")
}
ehloBuf := make([]byte, len(ehlo))
copy(ehloBuf, ehlo)
_, _, _ = p.mediateOnDownstream(ehloBuf, len(ehloBuf))
if !p.readytls.Load() {
t.Fatalf("expected p.readytls=true after handling a real EHLO STARTTLS response, got false")
}
if !p.ehloResponseHandled.Load() {
t.Fatalf("expected ehloResponseHandled=true after first EHLO response, got false")
}
// Now simulate the dangerous case: a downstream chunk that mimics a
// mail body containing both "250" and "STARTTLS" (including the literal
// "250-STARTTLS\r\n" sequence). The classifier must return false and
// removeStartTLSCommand must NOT be invoked.
body := []byte("From: alice@example.test\r\n" +
"To: bob@example.local\r\n" +
"Subject: TLS notes\r\n" +
"\r\n" +
"Our EHLO continuation looks like 250-STARTTLS\r\n" +
"...and that is the issue.\r\n")
if p.isResponseOfEHLOWithStartTLS(body) {
t.Fatalf("expected classifier to suppress misfire on mail-body chunk after EHLO was handled, got true")
}
// Reset p.readytls so we can assert mediateOnDownstream does NOT set it.
p.readytls.Store(false)
bodyBuf := make([]byte, len(body))
copy(bodyBuf, body)
_, _, _ = p.mediateOnDownstream(bodyBuf, len(bodyBuf))
if p.readytls.Load() {
t.Fatalf("expected p.readytls=false after mail-body chunk; got true (misfire regression)")
}
}
// TestDetectDataTerminator_SplitAcrossChunks verifies that the upstream
// fast-path terminator detector finds "\r\n.\r\n" even when it straddles
// a TCP read boundary. Without the tail carry-over, a chunk pair like
// ("...body\r\n.", "\r\n") would leave inDataPhase=true forever and
// cause subsequent SMTP commands to be relayed as body bytes.
func TestDetectDataTerminator_SplitAcrossChunks(t *testing.T) {
tests := []struct {
name string
chunks [][]byte
}{
{
name: "single chunk",
chunks: [][]byte{[]byte("body bytes\r\n.\r\n")},
},
{
name: "split after first CR",
chunks: [][]byte{[]byte("body bytes\r"), []byte("\n.\r\n")},
},
{
name: "split after first LF",
chunks: [][]byte{[]byte("body bytes\r\n"), []byte(".\r\n")},
},
{
name: "split after dot",
chunks: [][]byte{[]byte("body bytes\r\n."), []byte("\r\n")},
},
{
name: "split after trailing CR",
chunks: [][]byte{[]byte("body bytes\r\n.\r"), []byte("\n")},
},
{
name: "each byte of terminator in its own chunk",
chunks: [][]byte{[]byte("body bytes"), []byte("\r"), []byte("\n"), []byte("."), []byte("\r"), []byte("\n")},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := newPerfPipe()
var found bool
for _, ch := range tc.chunks {
if p.detectDataTerminator(ch) {
found = true
break
}
}
if !found {
t.Fatalf("terminator not detected across chunks %v", tc.chunks)
}
})
}
}
// TestDetectDataTerminator_NoFalsePositive ensures the detector does not
// trip on body bytes that merely contain individual terminator characters
// without forming the full "\r\n.\r\n" sequence.
func TestDetectDataTerminator_NoFalsePositive(t *testing.T) {
p := newPerfPipe()
// Body containing dots, CRLFs, and ".." (dot-stuffed) lines but never
// the literal "\r\n.\r\n" terminator.
chunks := [][]byte{
[]byte("Line one with a period.\r\n"),
[]byte("..dot-stuffed leading dot\r\n"),
[]byte("more body\r\n"),
}
for _, ch := range chunks {
if p.detectDataTerminator(ch) {
t.Fatalf("false positive on chunk %q", ch)
}
}
}