maddy

git clone git://git.lin.moe/fmaddy/maddy.git

  1//go:build integration
  2// +build integration
  3
  4/*
  5Maddy Mail Server - Composable all-in-one email server.
  6Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  7
  8This program is free software: you can redistribute it and/or modify
  9it under the terms of the GNU General Public License as published by
 10the Free Software Foundation, either version 3 of the License, or
 11(at your option) any later version.
 12
 13This program is distributed in the hope that it will be useful,
 14but WITHOUT ANY WARRANTY; without even the implied warranty of
 15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16GNU General Public License for more details.
 17
 18You should have received a copy of the GNU General Public License
 19along with this program.  If not, see <https://www.gnu.org/licenses/>.
 20*/
 21
 22package tests_test
 23
 24import (
 25	"sync"
 26	"testing"
 27	"time"
 28
 29	"github.com/foxcpp/maddy/tests"
 30)
 31
 32func floodSmtp(c *tests.Conn, commands, expectedPatterns []string, iterations int) {
 33	for i := 0; i < iterations; i++ {
 34		for i, cmd := range commands {
 35			c.Writeln(cmd)
 36			if expectedPatterns[i] != "" {
 37				c.ExpectPattern(expectedPatterns[i])
 38			}
 39		}
 40	}
 41}
 42
 43func TestSMTPFlood_FullMsg_NoLimits_1Conn(tt *testing.T) {
 44	tt.Parallel()
 45
 46	t := tests.NewT(tt)
 47	t.DNS(nil)
 48	t.Port("smtp")
 49	t.Config(`
 50		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
 51			hostname mx.maddy.test
 52			tls off
 53
 54			deliver_to dummy
 55		}`)
 56	t.Run(1)
 57	defer t.Close()
 58
 59	c := t.Conn("smtp")
 60	defer c.Close()
 61	c.SMTPNegotation("helo.maddy.test", nil, nil)
 62	floodSmtp(&c, []string{
 63		"MAIL FROM:<from@maddy.test>",
 64		"RCPT TO:<to@maddy.test>",
 65		"DATA",
 66		"From: <from@maddy.test>",
 67		"",
 68		"Heya!",
 69		".",
 70	}, []string{
 71		"250 *",
 72		"250 *",
 73		"354 *",
 74		"",
 75		"",
 76		"",
 77		"250 *",
 78	}, 100)
 79}
 80
 81func TestSMTPFlood_FullMsg_NoLimits_10Conns(tt *testing.T) {
 82	tt.Parallel()
 83
 84	t := tests.NewT(tt)
 85	t.DNS(nil)
 86	t.Port("smtp")
 87	t.Config(`
 88		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
 89			hostname mx.maddy.test
 90			tls off
 91
 92			deliver_to dummy
 93		}`)
 94	t.Run(1)
 95	defer t.Close()
 96
 97	wg := sync.WaitGroup{}
 98	for i := 0; i < 10; i++ {
 99		wg.Add(1)
100		go func() {
101			defer wg.Done()
102			c := t.Conn("smtp")
103			defer c.Close()
104			c.SMTPNegotation("helo.maddy.test", nil, nil)
105			floodSmtp(&c, []string{
106				"MAIL FROM:<from@maddy.test>",
107				"RCPT TO:<to@maddy.test>",
108				"DATA",
109				"From: <from@maddy.test>",
110				"",
111				"Heya!",
112				".",
113			}, []string{
114				"250 *",
115				"250 *",
116				"354 *",
117				"",
118				"",
119				"",
120				"250 *",
121			}, 100)
122			t.Log("Done")
123		}()
124	}
125
126	wg.Wait()
127}
128
129func TestSMTPFlood_EnvelopeAbort_NoLimits_10Conns(tt *testing.T) {
130	tt.Parallel()
131
132	t := tests.NewT(tt)
133	t.DNS(nil)
134	t.Port("smtp")
135	t.Config(`
136		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
137			hostname mx.maddy.test
138			tls off
139
140			deliver_to dummy
141		}`)
142	t.Run(1)
143	defer t.Close()
144
145	wg := sync.WaitGroup{}
146	for i := 0; i < 10; i++ {
147		wg.Add(1)
148		go func() {
149			defer wg.Done()
150			c := t.Conn("smtp")
151			defer c.Close()
152			c.SMTPNegotation("helo.maddy.test", nil, nil)
153			floodSmtp(&c, []string{
154				"MAIL FROM:<from@maddy.test>",
155				"RCPT TO:<to@maddy.test>",
156				"RSET",
157			}, []string{
158				"250 *",
159				"250 *",
160				"250 *",
161			}, 100)
162			t.Log("Done")
163		}()
164	}
165
166	wg.Wait()
167}
168
169func TestSMTPFlood_EnvelopeAbort_Ratelimited(tt *testing.T) {
170	tt.Parallel()
171
172	t := tests.NewT(tt)
173	t.DNS(nil)
174	t.Port("smtp")
175	t.Config(`
176		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
177			hostname mx.maddy.test
178			tls off
179
180			limits {
181				all rate 10 1s
182			}
183
184			deliver_to dummy
185		}`)
186	t.Run(1)
187	defer t.Close()
188
189	conns := 5
190	msgsPerConn := 10
191	expectedRate := 10
192	slip := 10
193
194	start := time.Now()
195
196	wg := sync.WaitGroup{}
197	for i := 0; i < conns; i++ {
198		wg.Add(1)
199		go func() {
200			defer wg.Done()
201			c := t.Conn("smtp")
202			defer c.Close()
203			c.SMTPNegotation("helo.maddy.test", nil, nil)
204			floodSmtp(&c, []string{
205				"MAIL FROM:<from@maddy.test>",
206				"RCPT TO:<to@maddy.test>",
207				"RSET",
208			}, []string{
209				"250 *",
210				"250 *",
211				"250 *",
212			}, msgsPerConn)
213			t.Log("Done")
214		}()
215	}
216
217	wg.Wait()
218	end := time.Now()
219
220	t.Log("Sent", conns*msgsPerConn, "messages using", conns, "connections")
221	t.Log("Took", end.Sub(start))
222
223	effectiveRate := float64(conns*msgsPerConn) / end.Sub(start).Seconds()
224	if effectiveRate > float64(expectedRate+slip) {
225		t.Fatal("Effective rate is significantly bigger than limit:", effectiveRate)
226	}
227	t.Log("Effective rate:", effectiveRate)
228}
229
230func TestSMTPFlood_FullMsg_Ratelimited_PerSource(tt *testing.T) {
231	tt.Parallel()
232
233	t := tests.NewT(tt)
234	t.DNS(nil)
235	t.Port("smtp")
236	t.Config(`
237		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
238			hostname mx.maddy.test
239			tls off
240
241			defer_sender_reject false
242
243			limits {
244				source rate 10 1s
245			}
246
247			deliver_to dummy
248		}`)
249	t.Run(1)
250	defer t.Close()
251
252	conns := 5
253	msgsPerConn := 10
254	expectedRate := 10
255	slip := 10
256
257	start := time.Now()
258
259	wg := sync.WaitGroup{}
260	for i := 0; i < conns; i++ {
261		wg.Add(1)
262		go func() {
263			defer wg.Done()
264			c := t.Conn("smtp")
265			defer c.Close()
266			c.SMTPNegotation("helo.maddy.test", nil, nil)
267			floodSmtp(&c, []string{
268				"MAIL FROM:<from@1.maddy.test>",
269				"RCPT TO:<to@maddy.test>",
270				"DATA",
271				"From: <from@1.maddy.test>",
272				"",
273				"Heya!",
274				".",
275			}, []string{
276				"250 *",
277				"250 *",
278				"354 *",
279				"",
280				"",
281				"",
282				"250 *",
283			}, msgsPerConn)
284			t.Log("Done")
285		}()
286	}
287	for i := 0; i < conns; i++ {
288		wg.Add(1)
289		go func() {
290			defer wg.Done()
291			c := t.Conn("smtp")
292			defer c.Close()
293			c.SMTPNegotation("helo.maddy.test", nil, nil)
294			floodSmtp(&c, []string{
295				"MAIL FROM:<from@2.maddy.test>",
296				"RCPT TO:<to@maddy.test>",
297				"DATA",
298				"From: <from@1.maddy.test>",
299				"",
300				"Heya!",
301				".",
302			}, []string{
303				"250 *",
304				"250 *",
305				"354 *",
306				"",
307				"",
308				"",
309				"250 *",
310			}, msgsPerConn)
311			t.Log("Done")
312		}()
313	}
314
315	wg.Wait()
316	end := time.Now()
317
318	t.Log("Sent", conns*msgsPerConn, "messages using", conns, "connections")
319	t.Log("Took", end.Sub(start))
320
321	effectiveRate := float64(conns*msgsPerConn*2) / end.Sub(start).Seconds()
322	// Expect the rate twice since we send from two sources.
323	if effectiveRate > float64(expectedRate*2+slip) {
324		t.Fatal("Effective rate is significantly bigger than limit:", effectiveRate)
325	}
326	t.Log("Effective rate:", effectiveRate)
327}
328
329func TestSMTPFlood_EnvelopeAbort_Ratelimited_PerIP(tt *testing.T) {
330	tt.Parallel()
331
332	t := tests.NewT(tt)
333	t.DNS(nil)
334	t.Port("smtp")
335	t.Config(`
336		smtp tcp://127.0.0.1:{env:TEST_PORT_smtp} {
337			hostname mx.maddy.test
338			tls off
339
340			defer_sender_reject false
341
342			limits {
343				ip rate 10 1s
344			}
345
346			deliver_to dummy
347		}`)
348	t.Run(1)
349	defer t.Close()
350
351	conns := 2
352	msgsPerConn := 50
353	expectedRate := 10
354	slip := 10
355
356	start := time.Now()
357
358	wg := sync.WaitGroup{}
359	for i := 0; i < conns; i++ {
360		wg.Add(1)
361		go func() {
362			defer wg.Done()
363			c := t.Conn4("127.0.0.1", "smtp")
364			defer c.Close()
365			c.SMTPNegotation("helo.maddy.test", nil, nil)
366			floodSmtp(&c, []string{
367				"MAIL FROM:<from@maddy.test>",
368				"RCPT TO:<to@maddy.test>",
369				"RSET",
370			}, []string{
371				"250 *",
372				"250 *",
373				"250 *",
374			}, msgsPerConn)
375			t.Log("Done")
376		}()
377	}
378	for i := 0; i < conns; i++ {
379		wg.Add(1)
380		go func() {
381			defer wg.Done()
382			c := t.Conn4("127.0.0.2", "smtp")
383			defer c.Close()
384			c.SMTPNegotation("helo.maddy.test", nil, nil)
385			floodSmtp(&c, []string{
386				"MAIL FROM:<from@maddy.test>",
387				"RCPT TO:<to@maddy.test>",
388				"RSET",
389			}, []string{
390				"250 *",
391				"250 *",
392				"250 *",
393			}, msgsPerConn)
394			t.Log("Done")
395		}()
396	}
397
398	wg.Wait()
399	end := time.Now()
400
401	t.Log("Sent", 2*conns*msgsPerConn, "messages using", conns*2, "connections")
402	t.Log("Took", end.Sub(start))
403
404	effectiveRate := float64(conns*msgsPerConn*2) / end.Sub(start).Seconds()
405	// Expect the rate twice since we send from two sources.
406	if effectiveRate > float64(expectedRate*2+slip) {
407		t.Fatal("Effective rate is significantly bigger than limit:", effectiveRate)
408	}
409	t.Log("Expected rate:", expectedRate*2)
410	t.Log("Effective rate:", effectiveRate)
411}