1//go:build integration2// +build integration34/*5Maddy Mail Server - Composable all-in-one email server.6Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors78This program is free software: you can redistribute it and/or modify9it under the terms of the GNU General Public License as published by10the Free Software Foundation, either version 3 of the License, or11(at your option) any later version.1213This program is distributed in the hope that it will be useful,14but WITHOUT ANY WARRANTY; without even the implied warranty of15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16GNU General Public License for more details.1718You should have received a copy of the GNU General Public License19along with this program. If not, see <https://www.gnu.org/licenses/>.20*/2122package tests_test2324import (25 "sync"26 "testing"27 "time"2829 "github.com/foxcpp/maddy/tests"30)3132func 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}4243func TestSMTPFlood_FullMsg_NoLimits_1Conn(tt *testing.T) {44 tt.Parallel()4546 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.test52 tls off5354 deliver_to dummy55 }`)56 t.Run(1)57 defer t.Close()5859 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}8081func TestSMTPFlood_FullMsg_NoLimits_10Conns(tt *testing.T) {82 tt.Parallel()8384 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.test90 tls off9192 deliver_to dummy93 }`)94 t.Run(1)95 defer t.Close()9697 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 }125126 wg.Wait()127}128129func TestSMTPFlood_EnvelopeAbort_NoLimits_10Conns(tt *testing.T) {130 tt.Parallel()131132 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.test138 tls off139140 deliver_to dummy141 }`)142 t.Run(1)143 defer t.Close()144145 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 }165166 wg.Wait()167}168169func TestSMTPFlood_EnvelopeAbort_Ratelimited(tt *testing.T) {170 tt.Parallel()171172 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.test178 tls off179180 limits {181 all rate 10 1s182 }183184 deliver_to dummy185 }`)186 t.Run(1)187 defer t.Close()188189 conns := 5190 msgsPerConn := 10191 expectedRate := 10192 slip := 10193194 start := time.Now()195196 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 }216217 wg.Wait()218 end := time.Now()219220 t.Log("Sent", conns*msgsPerConn, "messages using", conns, "connections")221 t.Log("Took", end.Sub(start))222223 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}229230func TestSMTPFlood_FullMsg_Ratelimited_PerSource(tt *testing.T) {231 tt.Parallel()232233 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.test239 tls off240241 defer_sender_reject false242243 limits {244 source rate 10 1s245 }246247 deliver_to dummy248 }`)249 t.Run(1)250 defer t.Close()251252 conns := 5253 msgsPerConn := 10254 expectedRate := 10255 slip := 10256257 start := time.Now()258259 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 }314315 wg.Wait()316 end := time.Now()317318 t.Log("Sent", conns*msgsPerConn, "messages using", conns, "connections")319 t.Log("Took", end.Sub(start))320321 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}328329func TestSMTPFlood_EnvelopeAbort_Ratelimited_PerIP(tt *testing.T) {330 tt.Parallel()331332 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.test338 tls off339340 defer_sender_reject false341342 limits {343 ip rate 10 1s344 }345346 deliver_to dummy347 }`)348 t.Run(1)349 defer t.Close()350351 conns := 2352 msgsPerConn := 50353 expectedRate := 10354 slip := 10355356 start := time.Now()357358 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 }397398 wg.Wait()399 end := time.Now()400401 t.Log("Sent", 2*conns*msgsPerConn, "messages using", conns*2, "connections")402 t.Log("Took", end.Sub(start))403404 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}