cookie
13 |
14 | // For test-wide assertions
15 | TotalTime []int64 // in ms
16 | FailCount int
17 | FailCountPerc float64 // should be in range [0,1]
18 | }
19 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_multipart_err.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://app.servdown.com/accounts/login/?next=/",
6 | "method": "GET",
7 | "payload_multipart": [
8 |
9 | {
10 | "name": "example-name-5",
11 | "value": "https://uplo333ad.wikimedia.org/wikipedia/commons/b/bd/Test.svg",
12 | "type": "file",
13 | "src": "remote"
14 | }
15 | ]
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | pull_request:
9 | branches:
10 | - master
11 | - develop
12 |
13 | jobs:
14 | link-checker:
15 | name: Check links
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout the repository
19 | uses: actions/checkout@v4
20 |
21 | - name: Check the links
22 | uses: lycheeverse/lychee-action@v1
23 | with:
24 | args: --max-concurrency 1 -v *.md **/*.md
25 | fail: true
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_global_envs.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "name": "Example Name 1",
6 | "url": "{{LOCAL}}",
7 | "method": "GET"
8 | },
9 | {
10 | "id": 2,
11 | "name": "Example Name 2 Json Body",
12 | "url": "{{HTTPBIN}}",
13 | "method": "GET",
14 | "headers": {
15 | "Content-Type": "application/json"
16 | }
17 | }
18 | ],
19 | "env":{
20 | "HTTPBIN" : "https://httpbin.ddosify.com",
21 | "LOCAL" : "http://localhost:8084/hello"
22 | }
23 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_protocol.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://app.servdown.com/accounts/login/?next=/",
6 | "protocol": "http"
7 | },
8 | {
9 | "id": 2,
10 | "url": "http://app.servdown.com/accounts/login/?next=/&112f12f12f12f"
11 | },
12 | {
13 | "id": 3,
14 | "url": "app.servdown.com/accounts/login/?next=/&112f12f12f12f"
15 | },
16 | {
17 | "id": 4,
18 | "url": "app.servdown.com/accounts/login/?next=/&112f12f12f12f",
19 | "protocol": "http"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/assertion/evaluator/function_test.go:
--------------------------------------------------------------------------------
1 | package evaluator
2 |
3 | import "testing"
4 |
5 | func TestEmptyArraysOnMinMaxAvgFuncs(t *testing.T) {
6 | empty := []int64{}
7 | _, err := min(empty)
8 | if err == nil {
9 | t.Errorf("expected error on empty array on min func")
10 | }
11 |
12 | _, err = max(empty)
13 | if err == nil {
14 | t.Errorf("expected error on empty array on max func")
15 | }
16 |
17 | _, err = avg(empty)
18 | if err == nil {
19 | t.Errorf("expected error on empty array on avg func")
20 | }
21 |
22 | _, err = percentile(empty, 99)
23 | if err == nil {
24 | t.Errorf("expected error on empty array on percentile func")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/race_configs/global_envs.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 10,
3 | "duration": 2,
4 | "steps": [
5 | {
6 | "id": 1,
7 | "name": "Example Name 1",
8 | "url": "{{LOCAL}}",
9 | "method": "GET"
10 | },
11 | {
12 | "id": 2,
13 | "name": "Example Name 2 Json Body",
14 | "url": "{{HTTPBIN}}",
15 | "method": "GET",
16 | "headers": {
17 | "Content-Type": "application/json"
18 | }
19 | }
20 | ],
21 | "env":{
22 | "HTTPBIN" : "https://httpbin.ddosify.com",
23 | "LOCAL" : "http://localhost:8084/hello"
24 | }
25 | }
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 | - package-ecosystem: "docker" # See documentation for possible values
13 | directory: "/" # Location of package manifests
14 | schedule:
15 | interval: "daily"
16 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_distinct_user.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "engine_mode": "ddosify",
4 | "load_type": "linear",
5 | "duration": 10,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "url": "{{HTTPBIN}}/json",
10 | "name": "JSON",
11 | "method": "GET",
12 | "others": {
13 | "h2": false,
14 | "disable-redirect": true,
15 | "disable-compression": false
16 | }
17 | }
18 | ],
19 | "output": "stdout",
20 | "env":{
21 | "HTTPBIN" : "https://httpbin.ddosify.com"
22 | },
23 | "debug" : false,
24 | "engine-mode": "distinct-user"
25 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_repeated_user.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "engine_mode": "ddosify",
4 | "load_type": "linear",
5 | "duration": 10,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "url": "{{HTTPBIN}}/json",
10 | "name": "JSON",
11 | "method": "GET",
12 | "others": {
13 | "h2": false,
14 | "disable-redirect": true,
15 | "disable-compression": false
16 | }
17 | }
18 | ],
19 | "output": "stdout",
20 | "env":{
21 | "HTTPBIN" : "https://httpbin.ddosify.com"
22 | },
23 | "debug" : false,
24 | "engine-mode": "distinct-user"
25 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/report/debug_test.go:
--------------------------------------------------------------------------------
1 | package report
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestDecode(t *testing.T) {
11 | h := http.Header{}
12 | h.Add("Content-Type", "application/json")
13 |
14 | type Temp struct {
15 | X float64 `json:"x"`
16 | }
17 |
18 | body := Temp{
19 | X: 52.2,
20 | }
21 |
22 | bBody, _ := json.Marshal(body)
23 |
24 | _, bodyDecoded, err := decode(h, bBody)
25 |
26 | if err != nil {
27 | t.Errorf("%v", err)
28 | }
29 |
30 | expected := reflect.ValueOf(map[string]interface{}{"x": 52.2})
31 | ei := expected.Interface()
32 | if !reflect.DeepEqual(ei, bodyDecoded) {
33 | t.Errorf("TestDecode, expected:%s got:%s", ei, bodyDecoded)
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 | **Is your feature request related to a problem? Please describe.**
9 |
10 |
11 |
12 | **Describe the solution you'd like**
13 |
14 |
15 |
16 | **Describe alternatives you've considered**
17 |
18 |
19 |
20 | **Additional context**
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | pull_request:
9 | branches:
10 | - master
11 | - develop
12 |
13 | jobs:
14 |
15 | test:
16 | strategy:
17 | matrix:
18 | go-version: [1.18.x, 1.19.x]
19 | os: [ubuntu-latest, macos-latest, windows-latest]
20 | runs-on: ${{ matrix.os }}
21 | steps:
22 | - uses: actions/checkout@v2
23 |
24 | - name: Set up Go
25 | uses: actions/setup-go@v2
26 | with:
27 | go-version: ${{ matrix.go-version }}
28 |
29 | - name: Build
30 | run: cd ddosify_engine && go build -race ./...
31 |
32 | - name: Test
33 | run: cd ddosify_engine && go test -parallel 1 -short ./...
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_test_assertion_fail.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "load_type": "linear",
4 | "duration": 10,
5 | "debug" : false,
6 | "success_criterias": [
7 | {
8 | "rule" : "false",
9 | "abort" : true,
10 | "delay" : 1
11 | }
12 | ],
13 | "steps": [
14 | {
15 | "id": 1,
16 | "url": "https://httpbin.ddosify.com/json2",
17 | "name": "JSON",
18 | "method": "GET",
19 | "others": {
20 | "h2": false,
21 | "keep-alive": true,
22 | "disable-redirect": true,
23 | "disable-compression": false
24 | },
25 | "timeout": 2
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 | ### Describe the bug
9 |
10 |
11 |
12 | ### To Reproduce
13 |
14 | Steps to reproduce the behavior:
15 |
16 | 1. Go to '...'
17 | 2. Click on '....'
18 | 3. Scroll down to '....'
19 | 4. See error
20 |
21 | ### Expected behavior
22 |
23 |
24 |
25 | ### Screenshots
26 |
27 |
28 |
29 | ### System (please complete the following information):
30 |
31 | - OS: [e.g. MacOS]
32 | - Anteon Version [e.g. v0.15.1]
33 |
34 | ### Additional context
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/race_configs/capture_envs.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 10,
3 | "duration": 2,
4 | "steps": [
5 | {
6 | "id": 1,
7 | "name": "Example Name 2 Json Body",
8 | "url": "{{HTTPBIN}}/json",
9 | "method": "GET",
10 | "headers": {
11 | "Content-Type": "application/json"
12 | },
13 | "capture_env": {
14 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"}
15 | }
16 | },
17 | {
18 | "id": 2,
19 | "name": "Example Name 1",
20 | "url": "{{LOCAL}}",
21 | "method": "GET"
22 | }
23 | ],
24 | "env":{
25 | "HTTPBIN" : "https://httpbin.ddosify.com",
26 | "LOCAL" : "http://localhost:8084/hello"
27 | }
28 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/util/buffer_pool.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | )
7 |
8 | // Factory is a function to create new connections.
9 | type BufferFactoryMethod func() *bytes.Buffer
10 | type BufferCloseMethod func(*bytes.Buffer)
11 |
12 | func NewBufferPool(initialCap, maxCap int, factory BufferFactoryMethod, close BufferCloseMethod) (*Pool[*bytes.Buffer], error) {
13 | if initialCap < 0 || maxCap <= 0 || initialCap > maxCap {
14 | return nil, errors.New("invalid capacity settings")
15 | }
16 |
17 | pool := &Pool[*bytes.Buffer]{
18 | Items: make(chan *bytes.Buffer, maxCap),
19 | Factory: factory,
20 | Close: close,
21 | }
22 |
23 | // create initial clients, if something goes wrong,
24 | // just close the pool error out.
25 | for i := 0; i < initialCap; i++ {
26 | client := pool.Factory()
27 | pool.Items <- client
28 | }
29 |
30 | return pool, nil
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: Coverage
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - develop
8 | pull_request:
9 | branches:
10 | - master
11 | - develop
12 |
13 | jobs:
14 |
15 | coverage:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v2
19 |
20 | - name: Set up Go
21 | uses: actions/setup-go@v2
22 | with:
23 | go-version: 1.18.x
24 |
25 | - name: Test
26 | run: cd ddosify_engine && go test -coverpkg=./... -coverprofile=coverage.txt -parallel 1 -covermode=atomic -short ./... && go tool cover -func coverage.txt
27 |
28 | - name: Upload reports to codecov
29 | run: |
30 | curl -Os https://uploader.codecov.io/latest/linux/codecov
31 | chmod +x codecov
32 | ./codecov -t ${CODECOV_TOKEN} -f coverage.txt
33 | env:
34 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
35 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_invalid_capture_env.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "{{LOCAL}}",
10 | "method": "GET",
11 | "capture_env": {
12 | "NUM" :{ "from":"body","json_path":"num"}
13 | }
14 | },
15 | {
16 | "id": 2,
17 | "name": "Example Name 2 Json Body",
18 | "url": "{{HTTPBIN}}",
19 | "method": "POST",
20 | "headers": {
21 | "Content-Type": "application/json",
22 | "num": "{{NUM}}"
23 | },
24 | "capture_env": {
25 | "REGEX_MATCH_ENV" :{"from":"header","regexp":{"exp" : "", "matchNo": 1}}
26 | }
27 | }
28 | ],
29 | "debug" : true
30 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "request_count": 1555,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "https://app.servdown.com/accounts/login/?next=/",
10 | "method": "GET",
11 | "payload": "payload str",
12 | "timeout": 3,
13 | "sleep": "1000",
14 | "others": {
15 | }
16 | },
17 | {
18 | "id": 2,
19 | "name": "Example Name 2",
20 | "url": "http://test.com",
21 | "method": "PUT",
22 | "headers": {
23 | "ContenType": "application/xml",
24 | "X-ddosify-key": "ajkndalnasd"
25 | },
26 | "timeout": 2,
27 | "sleep": " 300-500"
28 | }
29 | ],
30 | "output": "stdout",
31 | "proxy": "http://proxy_host:80"
32 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_iteration_count.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 1555,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "https://app.servdown.com/accounts/login/?next=/",
10 |
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | },
18 | {
19 | "id": 2,
20 | "name": "Example Name 2",
21 | "url": "http://test.com",
22 |
23 | "method": "PUT",
24 | "headers": {
25 | "ContenType": "application/xml",
26 | "X-ddosify-key": "ajkndalnasd"
27 | },
28 | "timeout": 2,
29 | "sleep": " 300-500"
30 | }
31 | ],
32 | "output": "stdout",
33 | "proxy": "http://proxy_host:80"
34 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/regex_test.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "strings"
5 | "testing"
6 | )
7 |
8 | func TestRegexExtractFromString(t *testing.T) {
9 | regex := "[a-z]+_[0-9]+"
10 |
11 | re := regexExtractor{}
12 | re.Init(regex)
13 |
14 | source := "messi_10alvarez_9"
15 |
16 | res, err2 := re.extractFromString(source, 1)
17 | if !strings.EqualFold(res, "alvarez_9") || err2 != nil {
18 | t.Errorf("RegexMatch should return second match")
19 | }
20 |
21 | res, err := re.extractFromString(source, 0)
22 | if !strings.EqualFold(res, "messi_10") || err != nil {
23 | t.Errorf("RegexMatch should return first match")
24 | }
25 |
26 | }
27 |
28 | func TestRegexExtractFromStringNoMatch(t *testing.T) {
29 | regex := "[a-z]+_[0-9]+"
30 |
31 | re := regexExtractor{}
32 | re.Init(regex)
33 |
34 | source := "messialvarez"
35 |
36 | _, err := re.extractFromString(source, 0)
37 | if err == nil {
38 | t.Errorf("Should be error %v", err)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_debug_false.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "iteration_count": 1555,
4 | "load_type": "waved",
5 | "duration": 21,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "name": "Example Name 1",
10 | "url": "https://app.servdown.com/accounts/login/?next=/",
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | },
18 | {
19 | "id": 2,
20 | "name": "Example Name 2",
21 | "url": "http://test.com",
22 | "method": "PUT",
23 | "headers": {
24 | "ContenType": "application/xml",
25 | "X-ddosify-key": "ajkndalnasd"
26 | },
27 | "timeout": 2,
28 | "sleep": " 300-500"
29 | }
30 | ],
31 | "output": "stdout",
32 | "proxy": "http://proxy_host:80"
33 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_debug_mode.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": true,
3 | "iteration_count": 1555,
4 | "load_type": "waved",
5 | "duration": 21,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "name": "Example Name 1",
10 | "url": "https://app.servdown.com/accounts/login/?next=/",
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | },
18 | {
19 | "id": 2,
20 | "name": "Example Name 2",
21 | "url": "http://test.com",
22 | "method": "PUT",
23 | "headers": {
24 | "ContenType": "application/xml",
25 | "X-ddosify-key": "ajkndalnasd"
26 | },
27 | "timeout": 2,
28 | "sleep": " 300-500"
29 | }
30 | ],
31 | "output": "stdout",
32 | "proxy": "http://proxy_host:80"
33 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_invalid_user_mode_for_cookies.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 1555,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "https://app.servdown.com/accounts/login/?next=/",
10 |
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | }
18 | ],
19 | "output": "stdout",
20 | "cookie_jar":{
21 | "enabled" : true,
22 | "cookies" :[
23 | {
24 | "name": "platform",
25 | "value": "web",
26 | "domain": "httpbin.ddosify.com",
27 | "path": "/",
28 | "expires": "Thu, 16 Mar 2023 09:24:02 GMT",
29 | "http_only": true,
30 | "secure": false
31 | }
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_iteration_count_over_req_count.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 333,
3 | "req_count": 222,
4 | "load_type": "waved",
5 | "duration": 21,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "name": "Example Name 1",
10 | "url": "https://app.servdown.com/accounts/login/?next=/",
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | },
18 | {
19 | "id": 2,
20 | "name": "Example Name 2",
21 | "url": "http://test.com",
22 | "method": "PUT",
23 | "headers": {
24 | "ContenType": "application/xml",
25 | "X-ddosify-key": "ajkndalnasd"
26 | },
27 | "timeout": 2,
28 | "sleep": " 300-500"
29 | }
30 | ],
31 | "output": "stdout",
32 | "proxy": "http://proxy_host:80"
33 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_init_cookies.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 1555,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "https://app.servdown.com/accounts/login/?next=/",
10 |
11 | "method": "GET",
12 | "payload": "payload str",
13 | "timeout": 3,
14 | "sleep": "1000",
15 | "others": {
16 | }
17 | }
18 | ],
19 | "output": "stdout",
20 | "engine_mode": "distinct-user",
21 | "cookie_jar":{
22 | "enabled" : true,
23 | "cookies" :[
24 | {
25 | "name": "platform",
26 | "value": "web",
27 | "domain": "httpbin.ddosify.com",
28 | "path": "/",
29 | "expires": "Thu, 16 Mar 2023 09:24:02 GMT",
30 | "http_only": true,
31 | "secure": false
32 | }
33 | ]
34 | }
35 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/util/pool.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | type Pool[T any] struct {
4 | Items chan T
5 | Factory func() T
6 | Close func(T)
7 | AfterPut func(T)
8 | }
9 |
10 | func (p *Pool[T]) Get() T {
11 | var item T
12 | select {
13 | case item = <-p.Items:
14 | default:
15 | item = p.Factory()
16 | }
17 | return item
18 | }
19 |
20 | func (p *Pool[T]) Put(item T) error {
21 | if p.Items == nil {
22 | // pool is closed, close passed client
23 | p.Close(item)
24 | return nil
25 | }
26 |
27 | // put the resource back into the pool. If the pool is full, this will
28 | // block and the default case will be executed.
29 | select {
30 | case p.Items <- item:
31 | if p.AfterPut != nil {
32 | p.AfterPut(item)
33 | }
34 | return nil
35 | default:
36 | // pool is full, close passed client
37 | p.Close(item)
38 | return nil
39 | }
40 | }
41 |
42 | func (p *Pool[T]) Len() int {
43 | return len(p.Items)
44 | }
45 |
46 | func (p *Pool[T]) Done() {
47 | close(p.Items)
48 | for i := range p.Items {
49 | p.Close(i)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_capture_environment.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "http://localhost:8080/hello",
10 | "method": "GET",
11 | "capture_env": {
12 | "NUM" :{ "from":"body","json_path":"num"},
13 | "X_COOKIE" :{ "from":"cookies","cookie_name":"x"}
14 | }
15 | },
16 | {
17 | "id": 2,
18 | "name": "Example Name 2 Json Body",
19 | "url": "http://localhost:8080/",
20 | "method": "POST",
21 | "headers": {
22 | "Content-Type": "application/json",
23 | "num": "{{NUM}}"
24 | },
25 | "capture_env": {
26 | "REGEX_MATCH_ENV" :{"from":"body","regexp":{"exp" : "[a-z]+_[0-9]+", "matchNo": 1}}
27 | }
28 | }
29 | ],
30 | "debug" : true
31 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/regex.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | )
7 |
8 | type regexExtractor struct {
9 | r *regexp.Regexp
10 | }
11 |
12 | func (ri *regexExtractor) Init(regex string) {
13 | ri.r = regexp.MustCompile(regex)
14 | }
15 |
16 | func (ri *regexExtractor) extractFromString(text string, matchNo int) (string, error) {
17 | matches := ri.r.FindAllString(text, -1)
18 |
19 | if matches == nil {
20 | return "", fmt.Errorf("no match for the Regex: %s Match no: %d", ri.r.String(), matchNo)
21 | }
22 |
23 | if len(matches) > matchNo {
24 | return matches[matchNo], nil
25 | }
26 | return matches[0], nil
27 | }
28 |
29 | func (ri *regexExtractor) extractFromByteSlice(text []byte, matchNo int) ([]byte, error) {
30 | matches := ri.r.FindAll(text, -1)
31 |
32 | if matches == nil {
33 | return nil, fmt.Errorf("no match for the Regex: %s Match no: %d", ri.r.String(), matchNo)
34 | }
35 |
36 | if len(matches) > matchNo {
37 | return matches[matchNo], nil
38 | }
39 | return matches[0], nil
40 | }
41 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_multipart_payload.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://app.servdown.com/accounts/login/?next=/",
6 | "method": "GET",
7 | "payload_multipart": [
8 | {
9 | "name": "example-name-1",
10 | "value": "config_testdata/test_img.svg",
11 | "type": "file"
12 | },
13 | {
14 | "name": "example-name-2",
15 | "value": "https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg",
16 | "type": "file",
17 | "src": "remote"
18 | },
19 | {
20 | "name": "example-name-3",
21 | "value": "text-field-value"
22 | },
23 | {
24 | "name": "example-name-4",
25 | "value": "123123",
26 | "type": "text"
27 | }
28 | ]
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_10rps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 10,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{_randomInt}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "duration": 10,
35 | "load_type": "linear",
36 | "iteration_count": 100
37 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/xml.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 |
7 | "github.com/antchfx/xmlquery"
8 | )
9 |
10 | type xmlExtractor struct {
11 | }
12 |
13 | func (xe xmlExtractor) extractFromByteSlice(source []byte, xPath string) (interface{}, error) {
14 | reader := bytes.NewBuffer(source)
15 | rootNode, err := xmlquery.Parse(reader)
16 | if err != nil {
17 | return nil, err
18 | }
19 |
20 | // returns the first matched element
21 | foundNode, err := xmlquery.Query(rootNode, xPath)
22 | if foundNode == nil || err != nil {
23 | return nil, fmt.Errorf("no match for the xPath: %s", xPath)
24 | }
25 |
26 | return foundNode.InnerText(), nil
27 | }
28 |
29 | func (xe xmlExtractor) extractFromString(source string, xPath string) (interface{}, error) {
30 | reader := bytes.NewBufferString(source)
31 | rootNode, err := xmlquery.Parse(reader)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | // returns the first matched element
37 | foundNode, err := xmlquery.Query(rootNode, xPath)
38 | if foundNode == nil || err != nil {
39 | return nil, fmt.Errorf("no match for this xpath")
40 | }
41 |
42 | return foundNode.InnerText(), nil
43 | }
44 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/requester/base_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package requester
22 |
23 | import (
24 | "reflect"
25 |
26 | "go.ddosify.com/ddosify/core/types"
27 | )
28 |
29 | var protocolStrategiesStructMap = map[string]reflect.Type{
30 | types.ProtocolHTTP: reflect.TypeOf(&HttpRequester{}),
31 | types.ProtocolHTTPS: reflect.TypeOf(&HttpRequester{}),
32 | }
33 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Ddosify
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | env:
15 | DOCKER_CLI_EXPERIMENTAL: "enabled"
16 | steps:
17 | -
18 | name: Checkout
19 | uses: actions/checkout@v2
20 | with:
21 | fetch-depth: 0
22 | -
23 | name: QEMU
24 | uses: docker/setup-qemu-action@v1
25 | -
26 | name: Docker Buildx
27 | uses: docker/setup-buildx-action@v1
28 | -
29 | name: Set up Go
30 | uses: actions/setup-go@v2
31 | with:
32 | go-version: 1.18
33 | -
34 | name: Docker Hub Login
35 | uses: docker/login-action@v1
36 | with:
37 | username: ${{ secrets.DOCKER_USERNAME }}
38 | password: ${{ secrets.DOCKER_PASSWORD }}
39 | -
40 | name: Run GoReleaser
41 | uses: goreleaser/goreleaser-action@v2
42 | with:
43 | distribution: goreleaser
44 | version: latest
45 | args: release --clean
46 | env:
47 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
48 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/html.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 |
7 | "github.com/antchfx/htmlquery"
8 | )
9 |
10 | type htmlExtractor struct {
11 | }
12 |
13 | func (xe htmlExtractor) extractFromByteSlice(source []byte, xPath string) (interface{}, error) {
14 | reader := bytes.NewBuffer(source)
15 | rootNode, err := htmlquery.Parse(reader)
16 | if err != nil {
17 | return nil, err
18 | }
19 |
20 | // returns the first matched element
21 | foundNode, err := htmlquery.Query(rootNode, xPath)
22 | if foundNode == nil || err != nil {
23 | return nil, fmt.Errorf("no match for the xPath_html: %s", xPath)
24 | }
25 |
26 | return foundNode.FirstChild.Data, nil
27 | }
28 |
29 | func (xe htmlExtractor) extractFromString(source string, xPath string) (interface{}, error) {
30 | reader := bytes.NewBufferString(source)
31 | rootNode, err := htmlquery.Parse(reader)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | // returns the first matched element
37 | foundNode, err := htmlquery.Query(rootNode, xPath)
38 | if foundNode == nil || err != nil {
39 | return nil, fmt.Errorf("no match for this xpath_html")
40 | }
41 |
42 | return foundNode.FirstChild.Data, nil
43 | }
44 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_100rps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 60,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{sandikID}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "env":{
35 | "sandikID" : "mamak75yil"
36 | },
37 | "duration": 10,
38 | "load_type": "linear",
39 | "iteration_count": 1000
40 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_1krps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 15,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{sandikID}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "env":{
35 | "sandikID" : "mamak75yil"
36 | },
37 | "duration": 10,
38 | "load_type": "linear",
39 | "iteration_count": 10000
40 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_200rps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 10,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{sandikID}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "env":{
35 | "sandikID" : "mamak75yil"
36 | },
37 | "duration": 10,
38 | "load_type": "linear",
39 | "iteration_count": 2000
40 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_2krps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 30,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{sandikID}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "env":{
35 | "sandikID" : "mamak75yil"
36 | },
37 | "duration": 10,
38 | "load_type": "linear",
39 | "iteration_count": 20000
40 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_500rps.json:
--------------------------------------------------------------------------------
1 | {
2 | "steps": [
3 | {
4 | "id": 1,
5 | "url": "https://testserver.ddosify.com/upload_image/",
6 | "name": "",
7 | "method": "POST",
8 | "others": {
9 | "h2": false,
10 | "keep-alive": true,
11 | "disable-redirect": true,
12 | "disable-compression": false
13 | },
14 | "headers": {
15 | "Content-Type": "multipart/form-data"
16 | },
17 | "timeout": 30,
18 | "capture_env": {},
19 | "payload_multipart": [
20 | {
21 | "src": "remote",
22 | "name": "image",
23 | "type": "file",
24 | "value": "https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png"
25 | },
26 | {
27 | "name": "ballot_id",
28 | "value": "{{sandikID}}"
29 | }
30 | ]
31 | }
32 | ],
33 | "output": "stdout",
34 | "env":{
35 | "sandikID" : "mamak75yil"
36 | },
37 | "duration": 10,
38 | "load_type": "linear",
39 | "iteration_count": 5000
40 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/assertion/token/token.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | type TokenType string
4 | type Token struct {
5 | Type TokenType
6 | Literal string
7 | }
8 |
9 | const (
10 | ILLEGAL = "ILLEGAL"
11 | EOF = "EOF"
12 |
13 | // Identifiers + literals
14 | IDENT = "IDENT" // not, equals, json_path, contains, range...
15 | INT = "INT" // 200, 201
16 | FLOAT = "FLOAT" // 10.5
17 | STRING = "STRING" // Content-Type
18 |
19 | // Operators
20 | PLUS = "+"
21 | MINUS = "-"
22 | BANG = "!"
23 | ASTERISK = "*"
24 | SLASH = "/"
25 | AND = "&&"
26 | OR = "||"
27 |
28 | LT = "<"
29 | GT = ">"
30 |
31 | EQ = "=="
32 | NOT_EQ = "!="
33 |
34 | // Delimiters
35 | COMMA = ","
36 |
37 | LPAREN = "("
38 | RPAREN = ")"
39 | LBRACE = "{"
40 | RBRACE = "}"
41 | LBRACKET = "["
42 | RBRACKET = "]"
43 |
44 | COLON = ":"
45 |
46 | // Keywords
47 | TRUE = "TRUE"
48 | FALSE = "FALSE"
49 | NULL = "NULL"
50 | )
51 |
52 | var keywords = map[string]TokenType{
53 | "true": TRUE,
54 | "false": FALSE,
55 | "null": NULL,
56 | }
57 |
58 | func LookupIdent(ident string) TokenType {
59 | if tok, ok := keywords[ident]; ok {
60 | return tok
61 | }
62 | return IDENT
63 | }
64 |
--------------------------------------------------------------------------------
/ddosify_engine/scripts/testing/benchstat.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | LIMIT=15
4 | IS_FAILED=0
5 | time_op=$(grep -A1 'time/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)
6 | echo -e "Max. Delta Time op: $time_op / $LIMIT" | tee benchstat.txt
7 | if (( $(echo "$time_op > $LIMIT" | bc -l) )); then
8 | IS_FAILED=1
9 | fi
10 |
11 | alloc_op=$(grep -A1 'alloc/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)
12 | echo -e "Max. Delta Alloc op: $alloc_op / $LIMIT" | tee --append benchstat.txt
13 | if (( $(echo "$alloc_op > $LIMIT" | bc -l) )); then
14 | IS_FAILED=1
15 | fi
16 |
17 | allocs_op=$(grep -A1 'allocs/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)
18 | echo -e "Max. Delta Allocs op: $allocs_op / $LIMIT" | tee --append benchstat.txt
19 | if (( $(echo "$allocs_op > $LIMIT" | bc -l) )); then
20 | IS_FAILED=1
21 | fi
22 |
23 | github_comment=`jq -Rs '.' benchstat.txt`
24 | curl -s -H "Authorization: token $1" \
25 | -X POST -d "{\"body\": $github_comment" \
26 | "https://api.github.com/repos/ddosify/ddosify/issues/$2/comments"
27 |
28 | if [ $IS_FAILED -eq 1 ]; then
29 | exit 1
30 | fi
31 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_incorrect.json:
--------------------------------------------------------------------------------
1 | {
2 | "request_count": 100,
3 | "load_type": "linear",
4 | "duration": 10,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "url": "https://app.servdown.com/accounts/login/?next=/",
9 | "auth": {
10 | "type": "basic",
11 | "username": "kursat",
12 | "password": "12345"
13 | },
14 | "method": "GET",
15 | "headers": {
16 | "ContenType": "application/xml",
17 | "User-Agent": "chrome5"
18 | },
19 | "payload": "body yt kanl adnlandlandaln",
20 | "timeout": 1,
21 | "others": {
22 | }
23 | },
24 | {
25 | "id": 2,
26 | "url": "https://app.servdown.com/accounts/login/?next=/&112f12f12f12f",
27 | "method": "GET",
28 | "headers": {
29 | "ContenType": "application/xml",
30 | "X-ddosify-key": "ajkndalnasd"
31 | },
32 | "payload_file": "config_examples/payload.txt",
33 | "timeout": 1,
34 | "others": {
35 | }
36 | },
37 | ],
38 | "proxy": "http://proxy_host:80",
39 | "output": "stdout"
40 | }
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 |
5 |
6 |
7 | ## Screenshots
8 |
9 |
10 |
11 | ## Type of Changes
12 |
13 |
14 |
15 | - [ ] Bug fix (non-breaking change which fixes an issue)
16 | - [ ] New feature (non-breaking change which adds functionality)
17 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
18 | - [ ] This change requires a documentation update
19 |
20 | ## Checklist
21 |
22 |
23 |
24 | - [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) document.
25 | - [ ] My code follows the code style of this project.
26 | - [ ] I have added tests to cover my changes.
27 | - [ ] All new and existing tests passed.
28 | - [ ] I have updated the [README.md](../README.md) as necessary if there are changes.
29 | - [ ] I have tested the changes on my local machine before submitting the PR.
30 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM golang:1.18.1
2 |
3 | WORKDIR /workspace
4 |
5 | COPY go.mod ./
6 | COPY go.sum ./
7 |
8 | ENV GOPATH /go
9 | ENV GOBIN /go/bin
10 |
11 | ENV LC_ALL=C.UTF-8
12 | ENV LANG=C.UTF-8
13 | ENV SHELL /bin/zsh
14 |
15 | RUN apt update && apt install -y git zsh vim fzf locales gcc musl-dev curl iputils-ping telnet graphviz bc jq && rm -rf /var/lib/apt/lists/*
16 | RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
17 | RUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
18 | RUN git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
19 | RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
20 | COPY .devcontainer/.zshrc /root/.zshrc
21 |
22 | RUN go install -v golang.org/x/tools/gopls@v0.8.3
23 | RUN go install -v github.com/rogpeppe/godef@v1.1.2
24 | RUN go install -v github.com/rakyll/gotest@v0.0.6
25 | RUN go install -v github.com/ramya-rao-a/go-outline@1.0.0
26 | RUN go install -v github.com/go-delve/delve/cmd/dlv@v1.8.1
27 | RUN go install -v golang.org/x/perf/cmd/benchstat@v0.0.0-20221222172245-91a04616dc65
28 | RUN go mod download
29 |
30 | CMD [ "zsh" ]
--------------------------------------------------------------------------------
/ddosify_engine/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM golang:1.18.1
2 |
3 | WORKDIR /workspace
4 |
5 | COPY go.mod ./
6 | COPY go.sum ./
7 |
8 | ENV GOPATH /go
9 | ENV GOBIN /go/bin
10 |
11 | ENV LC_ALL=C.UTF-8
12 | ENV LANG=C.UTF-8
13 | ENV SHELL /bin/zsh
14 |
15 | RUN apt update && apt install -y git zsh vim fzf locales gcc musl-dev curl iputils-ping telnet graphviz bc jq && rm -rf /var/lib/apt/lists/*
16 | RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
17 | RUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
18 | RUN git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
19 | RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
20 | COPY .devcontainer/.zshrc /root/.zshrc
21 |
22 | RUN go install -v golang.org/x/tools/gopls@v0.8.3
23 | RUN go install -v github.com/rogpeppe/godef@v1.1.2
24 | RUN go install -v github.com/rakyll/gotest@v0.0.6
25 | RUN go install -v github.com/ramya-rao-a/go-outline@1.0.0
26 | RUN go install -v github.com/go-delve/delve/cmd/dlv@v1.8.1
27 | RUN go install -v golang.org/x/perf/cmd/benchstat@v0.0.0-20221222172245-91a04616dc65
28 | RUN go mod download
29 |
30 | CMD [ "zsh" ]
--------------------------------------------------------------------------------
/ddosify_engine/completions/README.md:
--------------------------------------------------------------------------------
1 | # Shell completions
2 |
3 | ## Zsh
4 |
5 | `completions/_ddosify` provides a basic auto-completions. You can apply one of the steps to get an auto-completion successfully.
6 |
7 | You can locate the file in any directory referenced by `$fpath`. You can use the following command to list directories in `$fpath`.
8 |
9 | ```bash
10 | echo $fpath | tr ' ' '\n'
11 | ```
12 |
13 | For example, if you are using [oh-my-zsh](https://ohmyz.sh/) you can add it as a plugin after locating file under plugin related directory appeared in `$fpath`. You can create a directory named `ddosify` under `~/.oh-my-zsh/plugins` and copy `_ddosify` file to it.
14 |
15 | ```bash
16 | mkdir -p ~/.oh-my-zsh/plugins/ddosify
17 | cp completions/_ddosify ~/.oh-my-zsh/plugins/ddosify
18 | ```
19 |
20 | Then, you can add `ddosify` to your plugins list in `~/.zshrc` file.
21 |
22 | ```
23 | # ~/.zshrc
24 |
25 | plugins=(
26 | ...
27 | ddosify
28 | )
29 | ```
30 |
31 | If you don't have an appropriate directory, you can create one and add it to `$fpath`.
32 |
33 | ```
34 | mkdir -p ${ZDOTDIR:-~}/.zsh_functions
35 | echo 'fpath+=${ZDOTDIR:-~}/.zsh_functions' >> ${ZDOTDIR:-~}/.zshrc
36 | ```
37 |
38 | Then, you can copy `_ddosify` file to the directory you created.
39 |
40 | ```
41 | cp completions/_ddosify ${ZDOTDIR:-~}/.zsh_functions/_ddosify
42 | ```
43 |
--------------------------------------------------------------------------------
/selfhosted/nginx/default_reverseproxy.conf:
--------------------------------------------------------------------------------
1 | upstream frontend {
2 | server frontend:3000;
3 | }
4 |
5 | upstream backend {
6 | server backend:8008;
7 | }
8 |
9 | upstream alaz-backend {
10 | server alaz-backend:8008;
11 | }
12 |
13 | server {
14 | listen 80;
15 | client_max_body_size 96M;
16 | http2_max_field_size 64k;
17 | http2_max_header_size 512k;
18 |
19 | error_log /var/log/nginx/error.log error;
20 | access_log off;
21 |
22 | location / {
23 | proxy_pass http://frontend;
24 | proxy_set_header Host $host;
25 | proxy_set_header X-Real-IP $remote_addr;
26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
27 | proxy_set_header X-Forwarded-Proto $scheme;
28 | }
29 |
30 | location /api/ {
31 | proxy_pass http://backend;
32 | proxy_set_header Host $host;
33 | proxy_set_header X-Real-IP $remote_addr;
34 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 | proxy_set_header X-Forwarded-Proto $scheme;
36 | }
37 |
38 | location /api-alaz/ {
39 | proxy_pass http://alaz-backend;
40 | proxy_set_header Host $host;
41 | proxy_set_header X-Real-IP $remote_addr;
42 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
43 | proxy_set_header X-Forwarded-Proto $scheme;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ddosify Open Source",
3 | "build": {
4 | "dockerfile": "Dockerfile.dev",
5 | "context": "../"
6 | },
7 | "runArgs": [
8 | "-v",
9 | "${env:HOME}${env:USERPROFILE}/.ssh:/root/.ssh-localhost:ro",
10 | "--cap-add=SYS_PTRACE",
11 | "--security-opt",
12 | "seccomp=unconfined",
13 | "--ipc",
14 | "host",
15 | "--hostname",
16 | "ddosify"
17 | ],
18 | "customizations": {
19 | "vscode": {
20 | "settings": {
21 | "terminal.integrated.defaultProfile.linux": "zsh",
22 | "go.useLanguageServer": true,
23 | "go.gopath": "/go",
24 | "go.goroot": "/usr/local/go",
25 | "go.toolsGopath": "/go",
26 | "go.lintTool": "golangci-lint",
27 | "go.lintFlags": [
28 | "--config=${workspaceFolder}/.golangci.yml",
29 | "--fast"
30 | ],
31 | "files.eol": "\n"
32 | },
33 | "extensions": [
34 | "golang.Go",
35 | "eamodio.gitlens",
36 | "premparihar.gotestexplorer",
37 | "GitHub.copilot",
38 | "GitHub.copilot-labs"
39 | ]
40 | }
41 | },
42 | "postCreateCommand": "mkdir -p ~/.ssh && cp -r ~/.ssh-localhost/* ~/.ssh && chmod 700 ~/.ssh && chmod 600 ~/.ssh/*",
43 | "mounts": [
44 | // "source=${env:HOME}/.zsh_history,target=/root/.zsh_history,type=bind,consistency=cached"
45 | // "source=${env:HOME}/.bash_history,target=/root/.zsh_history,type=bind,consistency=cached"
46 | ]
47 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/proxy/base_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package proxy
22 |
23 | import (
24 | "testing"
25 | )
26 |
27 | func TestNewProxyService(t *testing.T) {
28 |
29 | // Valid proxy types
30 | for k := range AvailableProxyServices {
31 | _, err := NewProxyService(k)
32 |
33 | if err != nil {
34 | t.Errorf("TestNewProxyService errored %v", err)
35 | }
36 |
37 | }
38 |
39 | // Invalid proxy type
40 | _, err := NewProxyService("invalid_proxy_type")
41 | if err == nil {
42 | t.Errorf("TestNewProxyService invalid proxy should errored")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/ddosify_engine/go.mod:
--------------------------------------------------------------------------------
1 | module go.ddosify.com/ddosify
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/antchfx/xmlquery v1.3.13
7 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
8 | github.com/ddosify/go-faker v0.1.1
9 | github.com/enescakir/emoji v1.0.0
10 | github.com/fatih/color v1.13.0
11 | github.com/google/uuid v1.3.0
12 | github.com/mattn/go-colorable v0.1.12
13 | github.com/shirou/gopsutil/v3 v3.22.12
14 | github.com/tidwall/gjson v1.14.4
15 | golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
16 | golang.org/x/net v0.8.0
17 | )
18 |
19 | require (
20 | github.com/antchfx/htmlquery v1.3.0
21 | github.com/antchfx/xpath v1.2.3 // indirect
22 | github.com/go-ole/go-ole v1.2.6 // indirect
23 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
24 | github.com/jaswdr/faker v1.10.2 // indirect
25 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
26 | github.com/mattn/go-isatty v0.0.14 // indirect
27 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
28 | github.com/tidwall/match v1.1.1 // indirect
29 | github.com/tidwall/pretty v1.2.0 // indirect
30 | github.com/tklauser/go-sysconf v0.3.11 // indirect
31 | github.com/tklauser/numcpus v0.6.0 // indirect
32 | github.com/yusufpapurcu/wmi v1.2.2 // indirect
33 | golang.org/x/sys v0.6.0 // indirect
34 | golang.org/x/text v0.8.0 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/ddosify_engine/core/report/base_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package report
22 |
23 | import (
24 | "testing"
25 | )
26 |
27 | func TestNewReportService(t *testing.T) {
28 |
29 | // Valid output types
30 | for k := range AvailableOutputServices {
31 | _, err := NewReportService(k)
32 |
33 | if err != nil {
34 | t.Errorf("TestNewReportService %v", err)
35 | }
36 |
37 | }
38 |
39 | // Invalid output type
40 | _, err := NewReportService("invalid_output_type")
41 | if err == nil {
42 | t.Errorf("TestNewReportService invalid output should errored")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/ddosify_engine/core/util/helper.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package util
22 |
23 | import (
24 | "os"
25 | "strings"
26 | )
27 |
28 | // StringInSlice checks if the given string is in the given list of strings
29 | func StringInSlice(a string, list []string) bool {
30 | for _, b := range list {
31 | if b == a {
32 | return true
33 | }
34 | }
35 | return false
36 | }
37 |
38 | // IsSystemInTestMode checks if the system is running for tests.
39 | func IsSystemInTestMode() bool {
40 | for _, arg := range os.Args {
41 | if strings.HasPrefix(arg, "-test.") {
42 | return true
43 | }
44 | }
45 | return false
46 | }
47 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/injection/environment_dynamic.go:
--------------------------------------------------------------------------------
1 | package injection
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "reflect"
7 |
8 | "go.ddosify.com/ddosify/core/types/regex"
9 | )
10 |
11 | func (ei *EnvironmentInjector) InjectDynamic(text string) (string, error) {
12 | errors := []error{}
13 |
14 | injectStrFunc := getInjectStrFunc(regex.DynamicVariableRegex, ei, nil, &errors)
15 | injectToJsonByteFunc := getInjectJsonFunc(regex.JsonDynamicVariableRegex, ei, nil, &errors)
16 |
17 | // json injection
18 | bText := StringToBytes(text)
19 | if json.Valid(bText) {
20 | if ei.jr.Match(bText) {
21 | replacedBytes := ei.jdr.ReplaceAllFunc(bText, injectToJsonByteFunc)
22 | return string(replacedBytes), nil
23 | }
24 | }
25 |
26 | // string injection
27 | replaced := ei.dr.ReplaceAllStringFunc(text, injectStrFunc)
28 | if len(errors) == 0 {
29 | return replaced, nil
30 | }
31 |
32 | return replaced, unifyErrors(errors)
33 |
34 | }
35 |
36 | func (ei *EnvironmentInjector) getFakeData(key string) (interface{}, error) {
37 | var fakeFunc interface{}
38 | var keyExists bool
39 | if fakeFunc, keyExists = dynamicFakeDataMap[key]; !keyExists {
40 | return nil, fmt.Errorf("%s is not a valid dynamic variable", key)
41 | }
42 |
43 | preventRaceOnRandomFunc := func(fakeFunc interface{}) interface{} {
44 | ei.mu.Lock()
45 | defer ei.mu.Unlock()
46 | return reflect.ValueOf(fakeFunc).Call(nil)[0].Interface()
47 | }
48 |
49 | return preventRaceOnRandomFunc(fakeFunc), nil
50 | }
51 |
--------------------------------------------------------------------------------
/selfhosted/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |

4 |
5 |
6 | Anteon Self Hosted: Effortless Kubernetes Monitoring and Performance Testing
7 |
8 |
9 |
10 | Anteon detects high latency service calls on your K8s cluster. So you can easily find the root service causing the problem.
11 |
12 |
13 | ## 📚 Documentation
14 |
15 | - [🐝 Installing Anteon Self-Hosted](https://getanteon.com/docs/self-hosted/installation/)
16 | - [⚙ Installing eBPF Agent (Alaz)](https://getanteon.com/docs/self-hosted/install-ebpf-agent-alaz-on-self-hosted/)
17 | - [📰 Upgrading to Self-Hosted Enterprise](https://getanteon.com/docs/self-hosted/upgrading-to-self-hosted-enterprise/)
18 | - [📧 Slack Integration](https://getanteon.com/docs/self-hosted/self-hosted-slack-integration/)
19 |
20 | See the [documentation](https://getanteon.com/docs/self-hosted/) for other guides such as [disabling telemetry](https://getanteon.com/docs/self-hosted/disabling-telemetry-data/).
21 |
22 | ## 📝 License
23 |
24 | Anteon Self Hosted is licensed under the [AGPLv3](../LICENSE)
25 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_data_csv.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 4,
3 | "load_type": "waved",
4 | "duration": 1,
5 | "steps": [
6 | {
7 | "id": 2,
8 | "url": "{{LOCAL}}/body",
9 | "name": "JSON",
10 | "method": "GET",
11 | "others": {
12 | "h2": false,
13 | "disable-redirect": true,
14 | "disable-compression": false
15 | },
16 | "payload_file": "../config/config_testdata/data_json_payload.json",
17 | "timeout": 10
18 | }
19 | ],
20 | "output": "stdout",
21 | "env":{
22 | "HTTPBIN" : "https://httpbin.ddosify.com",
23 | "LOCAL" : "http://localhost:8084",
24 | "RANDOM_NAMES" : ["kenan","fatih","kursat","semih","sertac"] ,
25 | "RANDOM_INT" : [52,99,60,33],
26 | "RANDOM_BOOL" : [true,true,true,false]
27 | },
28 | "data":{
29 | "info": {
30 | "path" : "../config/config_testdata/test.csv",
31 | "src" : "local",
32 | "delimiter": ";",
33 | "vars": {
34 | "0":{"tag":"name"},
35 | "1":{"tag":"city"},
36 | "2":{"tag":"team"},
37 | "3":{"tag":"payload", "type":"json"},
38 | "4":{"tag":"age", "type":"int"}
39 | },
40 | "allow_quota" : true,
41 | "order": "random",
42 | "skip_first_line" : true
43 | }
44 | },
45 | "debug" : false
46 | }
--------------------------------------------------------------------------------
/ddosify_engine/completions/_ddosify:
--------------------------------------------------------------------------------
1 | #compdef ddosify _ddosify
2 |
3 | typeset -A opt_args
4 |
5 | _ddosify() {
6 | local curcontext="$curcontext" state line
7 | local -a opts
8 |
9 | opts+=(
10 | "-t[Target URL.]"
11 | "-P[Proxy address as protocol\://username\:password@host\:port. Supported proxies \[http(s), socks\].]"
12 | "-T[Request timeout in seconds (default 5).]"
13 | "-a[Basic authentication, username\:password.]"
14 | "-b[Payload of the network packet (body).]"
15 | "-cert_key_path[A path to a certificate key file (usually called 'key.pem').]:filename:_files"
16 | "-cert_path[A path to a certificate file (usually called 'cert.pem'.)]:filename:_files"
17 | "-config[Json config file path. If a config file is provided, other flag values will be ignored.]:filename:_files"
18 | "-d[Test duration in seconds (default 10).]"
19 | "-debug[Iterates the scenario once and prints curl-like verbose result.]"
20 | "-h[Request Headers. Ex\: -h 'Accept\: text/html' -h 'Content-Type\: application/xml'.]"
21 | "-l[Type of the load test \['linear', 'incremental', 'waved'\] (default 'linear').]"
22 | "-m[Request Method Type. For Http(s)\:\['GET', 'POST', 'PUT', 'DELETE', 'UPDATE', 'PATCH'\] (default 'GET').]"
23 | "-n[Total iteration count (default 100).]"
24 | "-o[Output destination (default 'stdout').]"
25 | "-version[Prints version, git commit, built date (utc), go information and quit.]"
26 | )
27 |
28 | _arguments -s -w : $opts && return 0
29 |
30 | return 1
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/race_configs/step_assertions_stdout.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "steps": [
4 | {
5 | "id": 1,
6 | "url": "https://testserver.ddosify.com/exchange/",
7 | "name": "",
8 | "method": "GET",
9 | "others": {
10 | "h2": false,
11 | "keep-alive": true,
12 | "disable-redirect": true,
13 | "disable-compression": false
14 | },
15 | "timeout": 10,
16 | "capture_env": {},
17 | "assertion": [
18 | "equals(status_code,203)",
19 | "contains(body,\"afssafs\")"
20 | ]
21 | },
22 | {
23 | "id": 2,
24 | "url": "https://testserver.ddosify.com/exchange/",
25 | "name": "",
26 | "method": "GET",
27 | "others": {
28 | "h2": false,
29 | "keep-alive": true,
30 | "disable-redirect": true,
31 | "disable-compression": false
32 | },
33 | "timeout": 10,
34 | "capture_env": {},
35 | "assertion": [
36 | "equals(status_code,401)"
37 | ]
38 | },
39 | {
40 | "id": 3,
41 | "url": "https://teasgsagasgsastserver.ddosify.com/exchange/",
42 | "name": "",
43 | "method": "GET",
44 | "others": {
45 | "h2": false,
46 | "keep-alive": true,
47 | "disable-redirect": true,
48 | "disable-compression": false
49 | },
50 | "timeout": 10,
51 | "capture_env": {},
52 | "assertion": [
53 | "equals(status_code,401)"
54 | ]
55 | }
56 | ],
57 | "iteration_count": 10,
58 | "duration": 2,
59 | "output": "stdout"
60 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/race_configs/step_assertions_stdout_json.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "steps": [
4 | {
5 | "id": 1,
6 | "url": "https://testserver.ddosify.com/exchange/",
7 | "name": "",
8 | "method": "GET",
9 | "others": {
10 | "h2": false,
11 | "keep-alive": true,
12 | "disable-redirect": true,
13 | "disable-compression": false
14 | },
15 | "timeout": 10,
16 | "capture_env": {},
17 | "assertion": [
18 | "equals(status_code,203)",
19 | "contains(body,\"afssafs\")"
20 | ]
21 | },
22 | {
23 | "id": 2,
24 | "url": "https://testserver.ddosify.com/exchange/",
25 | "name": "",
26 | "method": "GET",
27 | "others": {
28 | "h2": false,
29 | "keep-alive": true,
30 | "disable-redirect": true,
31 | "disable-compression": false
32 | },
33 | "timeout": 10,
34 | "capture_env": {},
35 | "assertion": [
36 | "equals(status_code,401)"
37 | ]
38 | },
39 | {
40 | "id": 3,
41 | "url": "https://teasgsagasgsastserver.ddosify.com/exchange/",
42 | "name": "",
43 | "method": "GET",
44 | "others": {
45 | "h2": false,
46 | "keep-alive": true,
47 | "disable-redirect": true,
48 | "disable-compression": false
49 | },
50 | "timeout": 10,
51 | "capture_env": {},
52 | "assertion": [
53 | "equals(status_code,401)"
54 | ]
55 | }
56 | ],
57 | "iteration_count": 10,
58 | "duration": 2,
59 | "output": "stdout-json"
60 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_inject_json.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "{{LOCAL}}",
10 | "method": "GET",
11 | "capture_env": {
12 | "NUM" :{ "from":"body","json_path":"num"},
13 | "NAME" :{ "from":"body","json_path":"name"},
14 | "IS_CHAMPION": {"from":"body","json_path":"isChampion"},
15 | "MESSI" : {"from":"body","json_path":"squad.players.0"},
16 | "PLAYERS" :{"from":"body","json_path":"squad.players"},
17 | "SQUAD" :{"from":"body","json_path":"squad"},
18 | "ARGENTINA" :{"from":"header", "header_key":"Argentina"},
19 | "m10" :{"from":"header", "header_key":"Argentina" ,"regexp":{"exp":"[a-z]+_[0-9]+","matchNo":1} }
20 | }
21 | },
22 | {
23 | "id": 2,
24 | "name": "Example Name 2 Json Body",
25 | "url": "{{LOCAL}}",
26 | "method": "POST",
27 | "headers": {
28 | "Content-Type": "application/json",
29 | "num": "{{NUM}}",
30 | "bool" : "{{IS_CHAMPION}}"
31 | },
32 | "payload_file" : "../config/config_testdata/json_payload.json",
33 | "capture_env": {
34 | "REGEX_MATCH_ENV" :{"from":"body","json_path":"num"}
35 | }
36 | }
37 | ],
38 | "env":{
39 | "HTTPBIN" : "https://httpbin.ddosify.com",
40 | "LOCAL" : "http://localhost:8084/hello"
41 | },
42 | "debug" : true
43 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/config_inject_json_dynamic.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "load_type": "waved",
4 | "duration": 21,
5 | "steps": [
6 | {
7 | "id": 1,
8 | "name": "Example Name 1",
9 | "url": "{{LOCAL}}",
10 | "method": "GET",
11 | "capture_env": {
12 | "NUM" :{ "from":"body","json_path":"num"},
13 | "NAME" :{ "from":"body","json_path":"name"},
14 | "IS_CHAMPION": {"from":"body","json_path":"isChampion"},
15 | "MESSI" : {"from":"body","json_path":"squad.players.0"},
16 | "PLAYERS" :{"from":"body","json_path":"squad.players"},
17 | "SQUAD" :{"from":"body","json_path":"squad"},
18 | "ARGENTINA" :{"from":"header", "header_key":"Argentina"},
19 | "m10" :{"from":"header", "header_key":"Argentina" ,"regexp":{"exp":"[a-z]+_[0-9]+","matchNo":1} }
20 | }
21 | },
22 | {
23 | "id": 2,
24 | "name": "Example Name 2 Json Body",
25 | "url": "{{LOCAL}}",
26 | "method": "POST",
27 | "headers": {
28 | "Content-Type": "application/json",
29 | "num": "{{NUM}}",
30 | "bool" : "{{IS_CHAMPION}}"
31 | },
32 | "payload_file" : "../config/config_testdata/json_payload_dynamic.json",
33 | "capture_env": {
34 | "REGEX_MATCH_ENV" :{"from":"body","json_path":"num"}
35 | }
36 | }
37 | ],
38 | "env":{
39 | "HTTPBIN" : "https://httpbin.ddosify.com",
40 | "LOCAL" : "http://localhost:8084/hello"
41 | },
42 | "debug" : true
43 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/base.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package config
22 |
23 | import (
24 | "fmt"
25 | "reflect"
26 |
27 | "go.ddosify.com/ddosify/core/types"
28 | )
29 |
30 | var AvailableConfigReader = make(map[string]ConfigReader)
31 |
32 | // ConfigReader is the interface that abstracts different config reader implementations.
33 | type ConfigReader interface {
34 | Init([]byte) error
35 | CreateHammer() (types.Hammer, error)
36 | }
37 |
38 | // NewConfigReader is the factory method of the ConfigReader.
39 | func NewConfigReader(config []byte, configType string) (reader ConfigReader, err error) {
40 | if val, ok := AvailableConfigReader[configType]; ok {
41 | // Create a new object from the service type
42 | reader = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ConfigReader)
43 | err = reader.Init(config)
44 | } else {
45 | err = fmt.Errorf("unsupported config reader type: %s", configType)
46 | }
47 | return
48 | }
49 |
--------------------------------------------------------------------------------
/ddosify_engine/core/report/base.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package report
22 |
23 | import (
24 | "fmt"
25 | "reflect"
26 |
27 | "go.ddosify.com/ddosify/core/assertion"
28 | "go.ddosify.com/ddosify/core/types"
29 | )
30 |
31 | var AvailableOutputServices = make(map[string]ReportService)
32 |
33 | // ReportService is the interface that abstracts different report implementations.
34 | type ReportService interface {
35 | DoneChan() <-chan bool
36 | Init(debug bool, samplingRate int) error
37 | Start(input chan *types.ScenarioResult, assertionResultChan <-chan assertion.TestAssertionResult)
38 | }
39 |
40 | // NewReportService is the factory method of the ReportService.
41 | func NewReportService(s string) (service ReportService, err error) {
42 | if val, ok := AvailableOutputServices[s]; ok {
43 | // Create a new object from the service type
44 | service = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ReportService)
45 | } else {
46 | err = fmt.Errorf("unsupported output type: %s", s)
47 | }
48 |
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/ddosify_engine/core/proxy/single.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package proxy
22 |
23 | import (
24 | "net/url"
25 | )
26 |
27 | const ProxyTypeSingle = "single"
28 |
29 | func init() {
30 | AvailableProxyServices[ProxyTypeSingle] = &singleProxyStrategy{}
31 | }
32 |
33 | type singleProxyStrategy struct {
34 | proxyAddr *url.URL
35 | }
36 |
37 | func (sp *singleProxyStrategy) Init(p Proxy) error {
38 | sp.proxyAddr = p.Addr
39 | return nil
40 | }
41 |
42 | // Since there is a 1 proxy, return that always
43 | func (sp *singleProxyStrategy) GetAll() []*url.URL {
44 | return []*url.URL{sp.proxyAddr}
45 | }
46 |
47 | // Since there is a 1 proxy, return that always
48 | func (sp *singleProxyStrategy) GetProxy() *url.URL {
49 | return sp.proxyAddr
50 | }
51 |
52 | func (sp *singleProxyStrategy) ReportProxy(addr *url.URL, reason string) *url.URL {
53 | return sp.proxyAddr
54 | }
55 |
56 | func (sp *singleProxyStrategy) GetProxyCountry(addr *url.URL) string {
57 | return "unknown"
58 | }
59 |
60 | func (sp *singleProxyStrategy) Done() error {
61 | return nil
62 | }
63 |
--------------------------------------------------------------------------------
/ddosify_engine/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | uname_arch() {
4 | arch=$(uname -m)
5 | case $arch in
6 | x86_64) arch="amd64" ;;
7 | x86) arch="386" ;;
8 | i686) arch="386" ;;
9 | i386) arch="386" ;;
10 | aarch64) arch="arm64" ;;
11 | armv*) arch="armv6" ;;
12 | armv*) arch="armv6" ;;
13 | armv*) arch="armv6" ;;
14 | esac
15 | if [ "$(uname_os)" == "darwin" ]; then
16 | arch="all"
17 | fi
18 | echo ${arch}
19 | }
20 |
21 | uname_os() {
22 | os=$(uname -s | tr '[:upper:]' '[:lower:]')
23 | echo "$os"
24 | }
25 |
26 |
27 |
28 | GITHUB_OWNER="ddosify"
29 | GITHUB_REPO="ddosify"
30 | TAG="latest"
31 | INSTALL_DIR="/usr/local/bin/"
32 | OS=$(uname_os)
33 | ARCH=$(uname_arch)
34 | PLATFORM="${OS}/${ARCH}"
35 | GITHUB_RELEASES_PAGE=https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases
36 | VERSION=$(curl $GITHUB_RELEASES_PAGE/$TAG -sL -H 'Accept:application/json' | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//' | tr -d v)
37 | NAME=${GITHUB_REPO}_${VERSION}_${OS}_${ARCH}
38 |
39 | TARBALL=${NAME}.tar.gz
40 | TARBALL_URL=${GITHUB_RELEASES_PAGE}/download/v${VERSION}/${TARBALL}
41 |
42 | echo "Downloading latest $GITHUB_REPO binary from $TARBALL_URL"
43 | tmpfolder=$(mktemp -d)
44 | $(curl $TARBALL_URL -sL -o $tmpfolder/$TARBALL)
45 |
46 | if [ ! -f $tmpfolder/$TARBALL ]; then
47 | echo "Can not download. Exiting..."
48 | exit 14
49 | fi
50 | cd ${tmpfolder} && tar --no-same-owner -xzf "$tmpfolder/$TARBALL"
51 |
52 | if [ ! -f $tmpfolder/$GITHUB_REPO ]; then
53 | echo "Can not find $GITHUB_REPO. Exiting..."
54 | exit 15
55 | fi
56 |
57 | binary=$tmpfolder/$GITHUB_REPO
58 | echo "Installing $GITHUB_REPO to $INSTALL_DIR (sudo access required to write to $INSTALL_DIR)"
59 | sudo install "$binary" $INSTALL_DIR
60 | echo "Installed $GITHUB_REPO to $INSTALL_DIR"
61 | echo "Simple usage: ddosify -t https://testserver.ddosify.com"
62 | rm -rf "${tmpdir}"
63 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/requester/base.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package requester
22 |
23 | import (
24 | "context"
25 | "net/http"
26 | "net/url"
27 |
28 | "go.ddosify.com/ddosify/core/scenario/scripting/injection"
29 | "go.ddosify.com/ddosify/core/types"
30 | )
31 |
32 | // Requester is the interface that abstracts different protocols' request sending implementations.
33 | // Protocol field in the types.ScenarioStep determines which requester implementation to use.
34 | type Requester interface {
35 | Type() string
36 | Done()
37 | }
38 |
39 | type HttpRequesterI interface {
40 | Init(ctx context.Context, ss types.ScenarioStep, url *url.URL, debug bool, ei *injection.EnvironmentInjector) error
41 | Send(client *http.Client, envs map[string]interface{}) *types.ScenarioStepResult // should use its own client if client is nil
42 | }
43 |
44 | // NewRequester is the factory method of the Requester.
45 | func NewRequester(s types.ScenarioStep) (requester Requester, err error) {
46 | requester = &HttpRequester{} // we have only HttpRequester type for now, add check for rpc in future
47 | return
48 | }
49 |
--------------------------------------------------------------------------------
/ddosify_engine/main_exit_test.go:
--------------------------------------------------------------------------------
1 | //go:build linux || darwin
2 | // +build linux darwin
3 |
4 | /*
5 | *
6 | * Ddosify - Load testing tool for any web system.
7 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
8 | *
9 | * This program is free software: you can redistribute it and/or modify
10 | * it under the terms of the GNU Affero General Public License as published
11 | * by the Free Software Foundation, either version 3 of the License, or
12 | * (at your option) any later version.
13 | *
14 | * This program is distributed in the hope that it will be useful,
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | * GNU Affero General Public License for more details.
18 | *
19 | * You should have received a copy of the GNU Affero General Public License
20 | * along with this program. If not, see .
21 | *
22 | */
23 |
24 | package main
25 |
26 | import (
27 | "fmt"
28 | "os"
29 | "syscall"
30 | "testing"
31 |
32 | "go.ddosify.com/ddosify/core/types"
33 | )
34 |
35 | func TestExitStatusOnTestFail(t *testing.T) {
36 | index := os.Getenv("index")
37 | if index == "" { // parent
38 | // start a test in child proc, look for its exit status
39 | env := fmt.Sprintf("index=%d", 1)
40 | cPid, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr{Files: []uintptr{0, 1, 2}, Env: []string{env}})
41 | if err != nil {
42 | panic(err.Error())
43 | }
44 |
45 | proc, err := os.FindProcess(cPid)
46 | if err != nil {
47 | panic(err.Error())
48 | }
49 |
50 | // expected child to fail with exit code 1
51 | pState, err := proc.Wait()
52 | if err != nil {
53 | panic(err.Error())
54 | }
55 | if pState.Success() {
56 | t.Fail()
57 | }
58 | } else {
59 | // run a failed engine
60 | *configPath = "config/config_testdata/config_test_assertion_fail.json"
61 | run = tempRun
62 | start()
63 | run = func(h types.Hammer) {}
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/assertion/assert.go:
--------------------------------------------------------------------------------
1 | package assertion
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator"
8 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/lexer"
9 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/parser"
10 | )
11 |
12 | type AssertionError struct { // UnWrappable
13 | failedAssertion string
14 | received map[string]interface{}
15 | wrappedErr error
16 | }
17 |
18 | func (ae AssertionError) Error() string {
19 | return fmt.Sprintf("input : %s, received: %v, wrappedErr: %v", ae.failedAssertion, ae.received, ae.wrappedErr)
20 | }
21 |
22 | func (ae AssertionError) Rule() string {
23 | return ae.failedAssertion
24 | }
25 |
26 | func (ae AssertionError) Received() map[string]interface{} {
27 | return ae.received
28 | }
29 |
30 | func (ae AssertionError) Unwrap() error {
31 | return ae.wrappedErr
32 | }
33 |
34 | func Assert(input string, env *evaluator.AssertEnv) (bool, error) {
35 | l := lexer.New(input)
36 | p := parser.New(l)
37 |
38 | node := p.ParseExpressionStatement()
39 | if len(p.Errors()) > 0 {
40 | return false, AssertionError{
41 | failedAssertion: input,
42 | received: map[string]interface{}{},
43 | wrappedErr: fmt.Errorf(strings.Join(p.Errors(), ",")),
44 | }
45 | }
46 |
47 | receivedMap := make(map[string]interface{})
48 | obj, err := evaluator.Eval(node, env, receivedMap)
49 | if err != nil {
50 | return false, AssertionError{
51 | failedAssertion: input,
52 | received: receivedMap,
53 | wrappedErr: err,
54 | }
55 | }
56 |
57 | b, ok := obj.(bool)
58 | if ok {
59 | if b == false {
60 | return false, AssertionError{
61 | failedAssertion: input,
62 | received: receivedMap,
63 | wrappedErr: fmt.Errorf("expression evaluated to false"),
64 | }
65 | }
66 | return b, nil
67 | }
68 |
69 | return false, AssertionError{
70 | failedAssertion: input,
71 | received: receivedMap,
72 | wrappedErr: fmt.Errorf("evaluated value is not bool : %v", obj),
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/assets/anteon-logo-db.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
--------------------------------------------------------------------------------
/assets/anteon-logo-wb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
--------------------------------------------------------------------------------
/ddosify_engine/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | dockerfile {
4 | filename '.devcontainer/Dockerfile.dev'
5 | }
6 | }
7 | environment {
8 | PROXY_TEST_USERNAME = credentials('proxy-test-username')
9 | PROXY_TEST_PASSWORD = credentials('proxy-test-password')
10 | }
11 |
12 | options {
13 | disableConcurrentBuilds()
14 | }
15 |
16 | stages {
17 | stage('Unit Test') {
18 | steps {
19 | sh 'go test -coverpkg=./... -coverprofile=coverage.out ./... -timeout 100s -parallel 4'
20 | }
21 | }
22 |
23 | stage('Coverage') {
24 | steps {
25 | sh 'go tool cover -html=coverage.out -o coverage.html'
26 | archiveArtifacts '*.html'
27 | sh 'echo "Coverage Report: ${BUILD_URL}artifact/coverage.html"'
28 | sh '''t=$(go tool cover -func coverage.out | grep total | tail -1 | awk \'{print substr($3, 1, length($3)-1)}\')
29 | if [ "${t%.*}" -lt 80 ]; then
30 | echo "Coverage failed ${t}/80"
31 | exit 1
32 | fi'''
33 | }
34 | }
35 |
36 | stage('Main Race Condition') {
37 | steps {
38 | lock('multi_branch_server') {
39 | sh 'go run --race main.go -t https://servdown.com/ -d 1 -n 1500'
40 | sh 'go run --race main.go -config config/config_testdata/race_configs/step_assertions_stdout.json'
41 | sh 'go run --race main.go -config config/config_testdata/race_configs/step_assertions_stdout_json.json'
42 | sh 'go run --race main.go -config config/config_testdata/race_configs/capture_envs.json'
43 | sh 'go run --race main.go -config config/config_testdata/race_configs/global_envs.json'
44 | sh 'go test -race -run ^TestDynamicVariableRace$ go.ddosify.com/ddosify/core/scenario/scripting/injection'
45 | }
46 | }
47 | }
48 |
49 | }
50 | post {
51 | unstable {
52 | slackSend(channel: '#jenkins', color: 'danger', message: "${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}")
53 | }
54 |
55 | failure {
56 | slackSend(channel: '#jenkins', color: 'danger', message: "${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}")
57 | }
58 |
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Anteon Security Policy 🐝
2 |
3 | We are committed to maintaining the security and integrity of [Anteon](https://github.com/getanteon/anteon), and we appreciate your help in identifying and addressing potential vulnerabilities.
4 |
5 | This document outlines the process for reporting security issues, as well as our commitment to addressing them in a timely manner.
6 |
7 | ## Supported Versions
8 |
9 | We provide security updates for the following versions:
10 |
11 | | Version | Supported |
12 | | ---------- | --------- |
13 | | > `0.15.x` | ✅ |
14 |
15 | ⚠️ Please note that older versions may not receive security updates. We encourage you to use the [latest version](https://github.com/getanteon/anteon/releases) of the software to benefit from the most recent security enhancements.
16 |
17 | ## Reporting a Vulnerability
18 |
19 | If you believe you have discovered a security vulnerability, please follow these steps to report it:
20 |
21 | 1. Do not create a public issue on GitHub. Disclosing security vulnerabilities publicly can put users at risk.
22 | 2. Send an email to our security team at security@getanteon.com with a detailed description of the vulnerability, including the steps to reproduce it, and any relevant information about your environment (e.g., OS, Go version, etc.).
23 | 3. If possible, provide a minimal code sample or test case that demonstrates the vulnerability.
24 | 4. Allow us a reasonable amount of time to investigate and address the issue before publicly disclosing it. We will make every effort to resolve the issue as soon as possible.
25 |
26 | We keep you informed of our progress in addressing the issue.
27 |
28 | ## Our Commitment
29 |
30 | We take security issues very seriously and are committed to working with you to address any vulnerabilities that you report. We will:
31 |
32 | - Investigate and validate reported vulnerabilities.
33 | - Work on a fix or mitigation for the issue.
34 | - Provide regular updates on our progress in resolving the issue.
35 | - Notify you when the issue has been resolved and provide details on the changes made.
36 | - We appreciate your assistance in maintaining the security of Anteon, and we thank you for your responsible disclosure.
37 |
--------------------------------------------------------------------------------
/ddosify_engine/core/proxy/base.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package proxy
22 |
23 | import (
24 | "fmt"
25 | "net/url"
26 | "reflect"
27 | )
28 |
29 | var AvailableProxyServices = make(map[string]ProxyService)
30 |
31 | // Proxy struct is used for initializing the ProxyService implementations.
32 | type Proxy struct {
33 | // Stragy of the proxy usage.
34 | Strategy string
35 |
36 | // Set this field if ProxyStrategy is single
37 | Addr *url.URL
38 |
39 | // Dynamic field for other proxy strategies.
40 | Others map[string]interface{}
41 | }
42 |
43 | // ProvideService is the interface that abstracts different proxy implementations.
44 | // Strategy field in types.Proxy determines which implementation to use.
45 | type ProxyService interface {
46 | Init(Proxy) error
47 | GetAll() []*url.URL
48 | GetProxy() *url.URL
49 | ReportProxy(addr *url.URL, reason string) *url.URL
50 | GetProxyCountry(*url.URL) string
51 | Done() error
52 | }
53 |
54 | // NewProxyService is the factory method of the ProxyService.
55 | func NewProxyService(s string) (service ProxyService, err error) {
56 | if val, ok := AvailableProxyServices[s]; ok {
57 | // Create a new object from the service type
58 | service = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ProxyService)
59 | } else {
60 | err = fmt.Errorf("unsupported proxy strategy: %s", s)
61 | }
62 |
63 | return
64 | }
65 |
--------------------------------------------------------------------------------
/ddosify_engine/config/base_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package config
22 |
23 | import (
24 | "io/ioutil"
25 | "os"
26 | "reflect"
27 | "testing"
28 | )
29 |
30 | func readConfigFile(path string) []byte {
31 | f, _ := os.Open(path)
32 |
33 | byteValue, _ := ioutil.ReadAll(f)
34 | return byteValue
35 | }
36 |
37 | func TestNewConfigReader(t *testing.T) {
38 | t.Parallel()
39 | configPath := "config_testdata/config.json"
40 | reader, err := NewConfigReader(readConfigFile(configPath), ConfigTypeJson)
41 |
42 | if err != nil {
43 | t.Errorf("TestNewConfigReader errored: %v", err)
44 | }
45 |
46 | if reflect.TypeOf(reader) != reflect.TypeOf(&JsonReader{}) {
47 | t.Errorf("Expected jsonReader found: %v", reflect.TypeOf(reader))
48 | }
49 | }
50 |
51 | func TestNewConfigReaderInvalidConfigType(t *testing.T) {
52 | t.Parallel()
53 | configPath := "config_testdata/config.json"
54 | _, err := NewConfigReader(readConfigFile(configPath), "invalidConfigType")
55 |
56 | if err == nil {
57 | t.Errorf("TestNewConfigReaderInvalidConfigType errored")
58 | }
59 | }
60 |
61 | func TestNewConfigReaderIncorrectJsonFile(t *testing.T) {
62 | t.Parallel()
63 | configPath := "config_testdata/config_incorrect.json"
64 | _, err := NewConfigReader(readConfigFile(configPath), ConfigTypeJson)
65 |
66 | if err == nil {
67 | t.Errorf("TestNewConfigReaderInvalidFilePath errored")
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/xml_test.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestXmlExtraction(t *testing.T) {
10 | expected := "XML Title"
11 | xmlSource := fmt.Sprintf(`
12 |
13 |
14 | -
15 | %s
16 |
17 |
18 | `, expected)
19 |
20 | xe := xmlExtractor{}
21 | xpath := "//item/title"
22 | val, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)
23 |
24 | if err != nil {
25 | t.Errorf("TestXmlExtraction %v", err)
26 | }
27 |
28 | if !strings.EqualFold(val.(string), expected) {
29 | t.Errorf("TestXmlExtraction expected: %s, got: %s", expected, val)
30 | }
31 | }
32 |
33 | func TestXmlExtractionString(t *testing.T) {
34 | expected := "XML Title"
35 | xmlSource := fmt.Sprintf(`
36 |
37 |
38 | -
39 | %s
40 |
41 |
42 | `, expected)
43 |
44 | xe := xmlExtractor{}
45 | xpath := "//item/title"
46 | val, err := xe.extractFromString(xmlSource, xpath)
47 |
48 | if err != nil {
49 | t.Errorf("TestXmlExtraction %v", err)
50 | }
51 |
52 | if !strings.EqualFold(val.(string), expected) {
53 | t.Errorf("TestXmlExtraction expected: %s, got: %s", expected, val)
54 | }
55 | }
56 |
57 | func TestXmlExtraction_PathNotFound(t *testing.T) {
58 | expected := "XML Title"
59 | xmlSource := fmt.Sprintf(`
60 |
61 |
62 | -
63 | %s
64 |
65 |
66 | `, expected)
67 |
68 | xe := xmlExtractor{}
69 | xpath := "//item3/title"
70 | _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)
71 |
72 | if err == nil {
73 | t.Errorf("TestXmlExtraction_PathNotFound, should be err, got :%v", err)
74 | }
75 | }
76 |
77 | func TestInvalidXml(t *testing.T) {
78 | xmlSource := `invalid xml source`
79 |
80 | xe := xmlExtractor{}
81 | xpath := "//item3/title"
82 | _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)
83 |
84 | if err == nil {
85 | t.Errorf("TestInvalidXml, should be err, got :%v", err)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/base_test.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "runtime"
7 | "testing"
8 |
9 | "go.ddosify.com/ddosify/core/types"
10 | )
11 |
12 | func TestHttpHeaderKey_NotSpecified(t *testing.T) {
13 | ce := types.EnvCaptureConf{
14 | JsonPath: nil,
15 | Xpath: nil,
16 | RegExp: &types.RegexCaptureConf{},
17 | Name: "",
18 | From: types.Header,
19 | Key: nil,
20 | }
21 |
22 | _, err := Extract(http.Header{}, ce)
23 |
24 | if err == nil {
25 | t.Errorf("Expected error when header key not specified")
26 | }
27 | }
28 |
29 | func TestExtract_TypeAssertErrorRecover(t *testing.T) {
30 | headerKey := "x"
31 | ce := types.EnvCaptureConf{
32 | JsonPath: nil,
33 | Xpath: nil,
34 | RegExp: nil,
35 | Name: "",
36 | From: types.Header,
37 | Key: &headerKey,
38 | }
39 |
40 | // source should be http.Header
41 | _, err := Extract("sdfds", ce)
42 |
43 | var assertError *runtime.TypeAssertionError
44 | if !errors.As(err, &assertError) {
45 | t.Errorf("Expected error must be TypeAssertionError, got %v", err)
46 | }
47 | }
48 |
49 | func TestExtract_NilSource(t *testing.T) {
50 | headerKey := "x"
51 | ce := types.EnvCaptureConf{
52 | JsonPath: nil,
53 | Xpath: nil,
54 | RegExp: nil,
55 | Name: "",
56 | From: types.Header,
57 | Key: &headerKey,
58 | }
59 |
60 | _, err := Extract(nil, ce)
61 |
62 | if err == nil {
63 | t.Errorf("error expected, got nil")
64 | }
65 | }
66 |
67 | func TestExtract_InvalidXml(t *testing.T) {
68 | xpath := ""
69 | ce := types.EnvCaptureConf{
70 | JsonPath: nil,
71 | Xpath: &xpath,
72 | RegExp: nil,
73 | Name: "",
74 | From: types.Body,
75 | Key: nil,
76 | }
77 |
78 | _, err := Extract([]byte("xxx"), ce)
79 |
80 | if err == nil {
81 | t.Errorf("error expected, got nil")
82 | }
83 | }
84 |
85 | func TestCookieName_NotSpecified(t *testing.T) {
86 | ce := types.EnvCaptureConf{
87 | JsonPath: nil,
88 | Xpath: nil,
89 | RegExp: &types.RegexCaptureConf{},
90 | Name: "",
91 | From: types.Cookie,
92 | Key: nil,
93 | CookieName: nil,
94 | }
95 |
96 | _, err := Extract(map[string]*http.Cookie{}, ce)
97 |
98 | if err == nil {
99 | t.Errorf("Expected error when cookie key not specified")
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/ddosify_engine/Jenkinsfile_benchmark:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | dockerfile {
4 | label 'performance-test'
5 | filename '.devcontainer/Dockerfile.dev'
6 | }
7 | }
8 |
9 | options {
10 | disableConcurrentBuilds()
11 | }
12 |
13 | stages {
14 | stage('Performance Test') {
15 | steps {
16 | lock('multi_branch_server_benchmark') {
17 | sh 'set -o pipefail && GOCACHE=/tmp/ go test -benchmem -timeout 60m -benchtime=1x -cpuprof=cpu.out -memprof=mem.out -tracef=trace.out -run=^$ -bench ^BenchmarkEngines/$ -count 1 -runN=10 | tee gobench_branch.txt'
18 | }
19 | }
20 | }
21 | stage('Performance Test Develop') {
22 | when {
23 | // Run on only PR
24 | allOf {
25 | expression { env.CHANGE_ID != null }
26 | expression { env.CHANGE_TARGET != null }
27 | expression { env.CHANGE_BRANCH != 'develop' }
28 | }
29 | }
30 | steps {
31 | lock('multi_branch_server_benchmark') {
32 | sh 'git fetch origin develop:develop || git checkout develop && git pull && set -o pipefail && GOCACHE=/tmp/ go test -benchmem -timeout 60m -benchtime=1x -cpuprof=cpu_develop.out -memprof=mem_develop.out -tracef=trace_develop.out -run=^$ -bench ^BenchmarkEngines/$ -count 1 -runN=10 | tee gobench_develop.txt'
33 | sh "git checkout ${BRANCH_NAME}"
34 | sh "benchstat -alpha 1.01 --sort delta gobench_develop.txt gobench_branch.txt | tee gobench_branch_result.txt"
35 | sh "benchstat -alpha 1.01 --sort delta --html gobench_develop.txt gobench_branch.txt > 00_gobench_result.html && echo ${BUILD_URL}artifact/00_gobench_result.html"
36 |
37 | withCredentials([usernamePassword(credentialsId: 'ddosifyadmin_comment_github_access', passwordVariable: 'GITHUB_TOKEN', usernameVariable: 'GITHUB_USERNAME')]) {
38 | sh "./scripts/testing/benchstat.sh ${GITHUB_TOKEN} ${env.CHANGE_ID}"
39 | }
40 | }
41 | }
42 | }
43 | }
44 | post {
45 | always {
46 | archiveArtifacts artifacts: '*.out', fingerprint: true
47 | archiveArtifacts artifacts: 'gobench_*.txt', fingerprint: true
48 | archiveArtifacts artifacts: '00_gobench_result.html', fingerprint: true, allowEmptyArchive: true
49 | }
50 | unstable {
51 | slackSend(channel: '#jenkins', color: 'danger', message: "${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}")
52 | }
53 |
54 | failure {
55 | slackSend(channel: '#jenkins', color: 'danger', message: "${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}")
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/json.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/tidwall/gjson"
9 | )
10 |
11 | type jsonExtractor struct {
12 | }
13 |
14 | var unmarshalJsonCapture = func(result gjson.Result) (interface{}, error) {
15 | bRaw := []byte(result.Raw)
16 | if result.IsObject() {
17 | jObject := map[string]interface{}{}
18 | err := json.Unmarshal(bRaw, &jObject)
19 | if err == nil {
20 | return jObject, err
21 | }
22 | }
23 |
24 | if result.IsArray() {
25 | jInterfaceSlice := []interface{}{}
26 | err := json.Unmarshal(bRaw, &jInterfaceSlice)
27 | if err == nil {
28 | return jInterfaceSlice, err
29 | }
30 | }
31 |
32 | if result.IsBool() {
33 | jBool := false
34 | err := json.Unmarshal(bRaw, &jBool)
35 | if err == nil {
36 | return jBool, err
37 | }
38 | }
39 |
40 | return nil, fmt.Errorf("json could not be unmarshaled")
41 | }
42 |
43 | func (je jsonExtractor) extractFromString(source string, jsonPath string) (interface{}, error) {
44 | result := gjson.Get(source, jsonPath)
45 |
46 | // path not found
47 | if result.Raw == "" && result.Type == gjson.Null {
48 | return "", fmt.Errorf("no match for the json path: %s", jsonPath)
49 | }
50 |
51 | switch result.Type {
52 | case gjson.String:
53 | return result.String(), nil
54 | case gjson.Null:
55 | return nil, nil
56 | case gjson.False:
57 | return false, nil
58 | case gjson.Number:
59 | number := result.String()
60 | if strings.Contains(number, ".") { // float
61 | return result.Float(), nil
62 | }
63 | return result.Int(), nil
64 | case gjson.True:
65 | return true, nil
66 | case gjson.JSON:
67 | return unmarshalJsonCapture(result)
68 | default:
69 | return "", nil
70 | }
71 | }
72 |
73 | func (je jsonExtractor) extractFromByteSlice(source []byte, jsonPath string) (interface{}, error) {
74 | result := gjson.GetBytes(source, jsonPath)
75 |
76 | // path not found
77 | if result.Raw == "" && result.Type == gjson.Null {
78 | return "", fmt.Errorf("no match for the json path: %s", jsonPath)
79 | }
80 |
81 | switch result.Type {
82 | case gjson.String:
83 | return result.String(), nil
84 | case gjson.Null:
85 | return nil, nil
86 | case gjson.False:
87 | return false, nil
88 | case gjson.Number:
89 | number := result.String()
90 | if strings.Contains(number, ".") { // float
91 | return result.Float(), nil
92 | }
93 | return result.Int(), nil
94 | case gjson.True:
95 | return true, nil
96 | case gjson.JSON:
97 | return unmarshalJsonCapture(result)
98 | default:
99 | return "", nil
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_correlation_load_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 100,
3 | "engine_mode": "ddosify",
4 | "load_type": "waved",
5 | "duration": 10,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "url": "{{HTTPBIN}}/json",
10 | "name": "JSON",
11 | "method": "GET",
12 | "others": {
13 | "h2": false,
14 | "disable-redirect": true,
15 | "disable-compression": false
16 | },
17 | "headers": {
18 |
19 | },
20 | "payload": "",
21 | "timeout": 3,
22 | "capture_env": {
23 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"},
24 | "STR" :{ "from":"body","json_path":"quoteResponse.result.0.currency"},
25 | "BOOL": {"from":"body","json_path":"quoteResponse.result.0.cryptoTradeable"},
26 | "FLOAT" : {"from":"body","json_path":"quoteResponse.result.0.epsForward"},
27 | "ALL_RESULT" :{"from":"body","json_path":"quoteResponse.result.0"},
28 | "CONTENT_LENGTH" :{"from":"header", "header_key":"Content-Length"},
29 | "CONTENT_TYPE" :{"from":"header", "header_key":"Content-Type" ,"regexp":{"exp":"application\/(\\w)+","matchNo":0} }
30 | }
31 | },
32 | {
33 | "id": 2,
34 | "url": "{{HTTPBIN}}/xml",
35 | "name": "XML",
36 | "method": "GET",
37 | "others": {
38 | "h2": false,
39 | "disable-redirect": true,
40 | "disable-compression": false
41 | },
42 | "headers": {
43 | "num": "{{NUM}}",
44 | "currency": "{{STR}}",
45 | "yahoo" : "{{CONTENT_LENGTH}}"
46 | },
47 | "payload": "",
48 | "timeout": 10
49 | },
50 | {
51 | "id": 3,
52 | "url": "https://servdown.com",
53 | "name": "HTML",
54 | "method": "GET",
55 | "others": {
56 | "h2": false,
57 | "disable-redirect": true,
58 | "disable-compression": false
59 | },
60 | "headers": {
61 | "num": "{{NUM}}"
62 | },
63 | "payload_file": "config/config_testdata/benchmark/json_payload.json",
64 | "timeout": 10
65 | }
66 | ],
67 | "output": "stdout",
68 | "env":{
69 | "HTTPBIN" : "https://httpbin.ddosify.com",
70 | "LOCAL" : "http://localhost:8084"
71 | },
72 | "debug" : false
73 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_correlation_load_2.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 1000,
3 | "load_type": "waved",
4 | "engine_mode": "ddosify",
5 | "duration": 10,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "url": "{{HTTPBIN}}/json",
10 | "name": "JSON",
11 | "method": "GET",
12 | "others": {
13 | "h2": false,
14 | "disable-redirect": true,
15 | "disable-compression": false
16 | },
17 | "headers": {
18 |
19 | },
20 | "payload": "",
21 | "timeout": 3,
22 | "capture_env": {
23 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"},
24 | "STR" :{ "from":"body","json_path":"quoteResponse.result.0.currency"},
25 | "BOOL": {"from":"body","json_path":"quoteResponse.result.0.cryptoTradeable"},
26 | "FLOAT" : {"from":"body","json_path":"quoteResponse.result.0.epsForward"},
27 | "ALL_RESULT" :{"from":"body","json_path":"quoteResponse.result.0"},
28 | "CONTENT_LENGTH" :{"from":"header", "header_key":"Content-Length"},
29 | "CONTENT_TYPE" :{"from":"header", "header_key":"Content-Type" ,"regexp":{"exp":"application\/(\\w)+","matchNo":0} }
30 | }
31 | },
32 | {
33 | "id": 2,
34 | "url": "{{HTTPBIN}}/xml",
35 | "name": "XML",
36 | "method": "GET",
37 | "others": {
38 | "h2": false,
39 | "disable-redirect": true,
40 | "disable-compression": false
41 | },
42 | "headers": {
43 | "num": "{{NUM}}",
44 | "currency": "{{STR}}",
45 | "yahoo" : "{{CONTENT_LENGTH}}"
46 | },
47 | "payload": "",
48 | "timeout": 10
49 | },
50 | {
51 | "id": 3,
52 | "url": "https://servdown.com",
53 | "name": "HTML",
54 | "method": "GET",
55 | "others": {
56 | "h2": false,
57 | "disable-redirect": true,
58 | "disable-compression": false
59 | },
60 | "headers": {
61 | "num": "{{NUM}}"
62 | },
63 | "payload_file": "config/config_testdata/benchmark/json_payload.json",
64 | "timeout": 10
65 | }
66 | ],
67 | "output": "stdout",
68 | "env":{
69 | "HTTPBIN" : "https://httpbin.ddosify.com",
70 | "LOCAL" : "http://localhost:8084"
71 | },
72 | "debug" : false
73 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_correlation_load_3.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 5000,
3 | "load_type": "waved",
4 | "engine_mode": "ddosify",
5 | "duration": 10,
6 | "steps": [
7 | {
8 | "id": 1,
9 | "url": "{{HTTPBIN}}/json",
10 | "name": "JSON",
11 | "method": "GET",
12 | "others": {
13 | "h2": false,
14 | "disable-redirect": true,
15 | "disable-compression": false
16 | },
17 | "headers": {
18 |
19 | },
20 | "payload": "",
21 | "timeout": 3,
22 | "capture_env": {
23 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"},
24 | "STR" :{ "from":"body","json_path":"quoteResponse.result.0.currency"},
25 | "BOOL": {"from":"body","json_path":"quoteResponse.result.0.cryptoTradeable"},
26 | "FLOAT" : {"from":"body","json_path":"quoteResponse.result.0.epsForward"},
27 | "ALL_RESULT" :{"from":"body","json_path":"quoteResponse.result.0"},
28 | "CONTENT_LENGTH" :{"from":"header", "header_key":"Content-Length"},
29 | "CONTENT_TYPE" :{"from":"header", "header_key":"Content-Type" ,"regexp":{"exp":"application\/(\\w)+","matchNo":0} }
30 | }
31 | },
32 | {
33 | "id": 2,
34 | "url": "{{HTTPBIN}}/xml",
35 | "name": "XML",
36 | "method": "GET",
37 | "others": {
38 | "h2": false,
39 | "disable-redirect": true,
40 | "disable-compression": false
41 | },
42 | "headers": {
43 | "num": "{{NUM}}",
44 | "currency": "{{STR}}",
45 | "yahoo" : "{{CONTENT_LENGTH}}"
46 | },
47 | "payload": "",
48 | "timeout": 10
49 | },
50 | {
51 | "id": 3,
52 | "url": "https://servdown.com",
53 | "name": "HTML",
54 | "method": "GET",
55 | "others": {
56 | "h2": false,
57 | "disable-redirect": true,
58 | "disable-compression": false
59 | },
60 | "headers": {
61 | "num": "{{NUM}}"
62 | },
63 | "payload_file": "config/config_testdata/benchmark/json_payload.json",
64 | "timeout": 10
65 | }
66 | ],
67 | "output": "stdout",
68 | "env":{
69 | "HTTPBIN" : "https://httpbin.ddosify.com",
70 | "LOCAL" : "http://localhost:8084"
71 | },
72 | "debug" : false
73 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_correlation_load_4.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 10000,
3 | "load_type": "waved",
4 | "engine_mode": "ddosify",
5 |
6 | "duration": 10,
7 | "steps": [
8 | {
9 | "id": 1,
10 | "url": "{{HTTPBIN}}/json",
11 | "name": "JSON",
12 | "method": "GET",
13 | "others": {
14 | "h2": false,
15 | "disable-redirect": true,
16 | "disable-compression": false
17 | },
18 | "headers": {
19 |
20 | },
21 | "payload": "",
22 | "timeout": 3,
23 | "capture_env": {
24 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"},
25 | "STR" :{ "from":"body","json_path":"quoteResponse.result.0.currency"},
26 | "BOOL": {"from":"body","json_path":"quoteResponse.result.0.cryptoTradeable"},
27 | "FLOAT" : {"from":"body","json_path":"quoteResponse.result.0.epsForward"},
28 | "ALL_RESULT" :{"from":"body","json_path":"quoteResponse.result.0"},
29 | "CONTENT_LENGTH" :{"from":"header", "header_key":"Content-Length"},
30 | "CONTENT_TYPE" :{"from":"header", "header_key":"Content-Type" ,"regexp":{"exp":"application\/(\\w)+","matchNo":0} }
31 | }
32 | },
33 | {
34 | "id": 2,
35 | "url": "{{HTTPBIN}}/xml",
36 | "name": "XML",
37 | "method": "GET",
38 | "others": {
39 | "h2": false,
40 | "disable-redirect": true,
41 | "disable-compression": false
42 | },
43 | "headers": {
44 | "num": "{{NUM}}",
45 | "currency": "{{STR}}",
46 | "yahoo" : "{{CONTENT_LENGTH}}"
47 | },
48 | "payload": "",
49 | "timeout": 10
50 | },
51 | {
52 | "id": 3,
53 | "url": "https://servdown.com",
54 | "name": "HTML",
55 | "method": "GET",
56 | "others": {
57 | "h2": false,
58 | "disable-redirect": true,
59 | "disable-compression": false
60 | },
61 | "headers": {
62 | "num": "{{NUM}}"
63 | },
64 | "payload_file": "config/config_testdata/benchmark/json_payload.json",
65 | "timeout": 10
66 | }
67 | ],
68 | "output": "stdout",
69 | "env":{
70 | "HTTPBIN" : "https://httpbin.ddosify.com",
71 | "LOCAL" : "http://localhost:8084"
72 | },
73 | "debug" : false
74 | }
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/benchmark/config_correlation_load_5.json:
--------------------------------------------------------------------------------
1 | {
2 | "iteration_count": 20000,
3 | "load_type": "waved",
4 | "engine_mode": "ddosify",
5 |
6 | "duration": 10,
7 | "steps": [
8 | {
9 | "id": 1,
10 | "url": "{{HTTPBIN}}/json",
11 | "name": "JSON",
12 | "method": "GET",
13 | "others": {
14 | "h2": false,
15 | "disable-redirect": true,
16 | "disable-compression": false
17 | },
18 | "headers": {
19 |
20 | },
21 | "payload": "",
22 | "timeout": 3,
23 | "capture_env": {
24 | "NUM" :{ "from":"body","json_path":"quoteResponse.result.0.askSize"},
25 | "STR" :{ "from":"body","json_path":"quoteResponse.result.0.currency"},
26 | "BOOL": {"from":"body","json_path":"quoteResponse.result.0.cryptoTradeable"},
27 | "FLOAT" : {"from":"body","json_path":"quoteResponse.result.0.epsForward"},
28 | "ALL_RESULT" :{"from":"body","json_path":"quoteResponse.result.0"},
29 | "CONTENT_LENGTH" :{"from":"header", "header_key":"Content-Length"},
30 | "CONTENT_TYPE" :{"from":"header", "header_key":"Content-Type" ,"regexp":{"exp":"application\/(\\w)+","matchNo":0} }
31 | }
32 | },
33 | {
34 | "id": 2,
35 | "url": "{{HTTPBIN}}/xml",
36 | "name": "XML",
37 | "method": "GET",
38 | "others": {
39 | "h2": false,
40 | "disable-redirect": true,
41 | "disable-compression": false
42 | },
43 | "headers": {
44 | "num": "{{NUM}}",
45 | "currency": "{{STR}}",
46 | "yahoo" : "{{CONTENT_LENGTH}}"
47 | },
48 | "payload": "",
49 | "timeout": 10
50 | },
51 | {
52 | "id": 3,
53 | "url": "https://servdown.com",
54 | "name": "HTML",
55 | "method": "GET",
56 | "others": {
57 | "h2": false,
58 | "disable-redirect": true,
59 | "disable-compression": false
60 | },
61 | "headers": {
62 | "num": "{{NUM}}"
63 | },
64 | "payload_file": "config/config_testdata/benchmark/json_payload.json",
65 | "timeout": 10
66 | }
67 | ],
68 | "output": "stdout",
69 | "env":{
70 | "HTTPBIN" : "https://httpbin.ddosify.com",
71 | "LOCAL" : "http://localhost:8084"
72 | },
73 | "debug" : false
74 | }
--------------------------------------------------------------------------------
/ddosify_engine/core/types/response.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package types
22 |
23 | import (
24 | "net/http"
25 | "net/url"
26 | "time"
27 |
28 | "github.com/google/uuid"
29 | )
30 |
31 | // ScenarioResult is corresponding to Scenario. Each Scenario has a ScenarioResult after the scenario is played.
32 | type ScenarioResult struct {
33 | // First request start time for the Scenario
34 | StartTime time.Time
35 |
36 | ProxyAddr *url.URL
37 | StepResults []*ScenarioStepResult
38 |
39 | // Dynamic field for extra data needs in response object consumers.
40 | Others map[string]interface{}
41 | }
42 |
43 | // ScenarioStepResult is corresponding to ScenarioStep.
44 | type ScenarioStepResult struct {
45 | // ID of the ScenarioStep
46 | StepID uint16
47 |
48 | // Name of the ScenarioStep
49 | StepName string
50 |
51 | // Each request has a unique ID.
52 | RequestID uuid.UUID
53 |
54 | // Returned status code. Has different meaning for different protocols.
55 | StatusCode int
56 |
57 | // Time of the request call.
58 | RequestTime time.Time
59 |
60 | // Total duration. From request sending to full response receiving.
61 | Duration time.Duration
62 |
63 | // Response content length
64 | ContentLength int64
65 |
66 | // Error occurred at request time.
67 | Err RequestError
68 |
69 | // Url
70 | Url string
71 |
72 | // Method
73 | Method string
74 |
75 | // Request Headers
76 | ReqHeaders http.Header
77 |
78 | // Request Body
79 | ReqBody []byte
80 |
81 | // Response Headers
82 | RespHeaders http.Header
83 |
84 | // Response Body
85 | RespBody []byte
86 |
87 | // Protocol specific metrics. For ex: DNSLookupDuration: 1s for HTTP
88 | Custom map[string]interface{}
89 |
90 | // Usable envs in this step
91 | UsableEnvs map[string]interface{}
92 |
93 | // Captured envs in this step
94 | ExtractedEnvs map[string]interface{}
95 |
96 | // Failed captures and their reasons
97 | FailedCaptures map[string]string
98 |
99 | // Failed assertion rules and received values
100 | FailedAssertions []FailedAssertion
101 | }
102 |
--------------------------------------------------------------------------------
/ddosify_engine/core/types/regex/regex_test.go:
--------------------------------------------------------------------------------
1 | package regex
2 |
3 | import (
4 | "regexp"
5 | "testing"
6 | )
7 |
8 | func TestDynamicVariableRegex(t *testing.T) {
9 | re := regexp.MustCompile(DynamicVariableRegex)
10 | // Sub Tests
11 | tests := []struct {
12 | name string
13 | url string
14 | shouldMatch bool
15 | }{
16 | {"Match1", "https://example.com/{{_abc}}", true},
17 | {"Match2", "https://example.com/{{_timestamp}}", true},
18 | {"Match3", "https://example.com/aaa/{{_timestamp}}", true},
19 | {"Match4", "https://example.com/aaa/{{_timestamp}}/bbb", true},
20 | {"Match5", "https://example.com/{{_timestamp}}/{_abc}", true},
21 | {"Match6", "https://example.com/{{_abc/{{_timestamp}}", true},
22 | {"Match7", "https://example.com/_aaa/{{_timestamp}}", true},
23 |
24 | {"Not Match1", "https://example.com/{{_abc", false},
25 | {"Not Match2", "https://example.com/{{_abc}", false},
26 | {"Not Match3", "https://example.com/_abc", false},
27 | {"Not Match4", "https://example.com/{{abc", false},
28 | {"Not Match5", "https://example.com/abc", false},
29 | {"Not Match6", "https://example.com/abc/{{cc}}", false},
30 | {"Not Match7", "https://example.com/abc/{{cc}}/fcf", false},
31 | }
32 |
33 | for _, test := range tests {
34 | tf := func(t *testing.T) {
35 | matched := re.MatchString(test.url)
36 |
37 | if test.shouldMatch != matched {
38 | t.Errorf("Name: %s, ShouldMatch: %t, Matched: %t\n", test.name, test.shouldMatch, matched)
39 | }
40 |
41 | }
42 | t.Run(test.name, tf)
43 | }
44 | }
45 |
46 | func TestEnvironmentVariableRegex(t *testing.T) {
47 | re := regexp.MustCompile(EnvironmentVariableRegex)
48 | // Sub Tests
49 | tests := []struct {
50 | name string
51 | url string
52 | shouldMatch bool
53 | }{
54 | {"Match1", "{{a}}", true},
55 | {"Match2", "{{ab}}", true},
56 | {"Match3", "{{ab1}}", true},
57 | {"Match4", "{{Ab1}}/bbb", true},
58 | {"Match5", "{{ABC}}/{_abc}", true},
59 | {"Match6", "{{_abc/{{ABC__fc_111}}", true},
60 | {"Match7", "{{a_b}}", true},
61 | {"Match8", "xx{{a}}", true},
62 | {"Match9", "{{a}}bb", true},
63 | {"Match10", "cx{{a}}vc", true},
64 | {"Match10", "cx {{a}}vc", true},
65 | {"Match11", "cx{{a}} vc", true},
66 | {"Match11", "cx{{a}}_-", true},
67 | {"Match12", "{{a-v}}", true},
68 | {"Match13", "{{AV-}}", true},
69 |
70 | {"Not Match1", "{{}}", false},
71 | {"Not Match2", "{{_abc}}", false},
72 | {"Not Match4", "{{abc!}}", false},
73 | {"Not Match6", "{{_A}}", false},
74 | {"Not Match7", "{{_AB_2}}", false},
75 | {"Not Match8", "{{£AB_2}}", false},
76 | {"Not Match8", "{{3AB_2}}", false},
77 | {"Not Match8", "{{%3AB_2}}", false},
78 | }
79 |
80 | for _, test := range tests {
81 | tf := func(t *testing.T) {
82 | matched := re.MatchString(test.url)
83 |
84 | if test.shouldMatch != matched {
85 | t.Errorf("Name: %s, ShouldMatch: %t, Matched: %t\n", test.name, test.shouldMatch, matched)
86 | }
87 |
88 | }
89 | t.Run(test.name, tf)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/html_test.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestHtmlExtraction(t *testing.T) {
10 | expected := "Html Title"
11 | HtmlSource := fmt.Sprintf(`
12 |
13 |
14 | %s
15 | My first paragraph.
16 |
17 | `, expected)
18 |
19 | xe := htmlExtractor{}
20 | xpath := "//body/h1"
21 | val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)
22 |
23 | if err != nil {
24 | t.Errorf("TestHtmlExtraction %v", err)
25 | }
26 |
27 | if !strings.EqualFold(val.(string), expected) {
28 | t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val)
29 | }
30 | }
31 |
32 | func TestHtmlExtractionSeveralNode(t *testing.T) {
33 | //should extract only the first one
34 | expected := "Html Title"
35 | HtmlSource := fmt.Sprintf(`
36 |
37 |
38 | %s
39 | another node
40 | My first paragraph.
41 |
42 | `, expected)
43 |
44 | xe := htmlExtractor{}
45 | xpath := "//h1"
46 | val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)
47 |
48 | if err != nil {
49 | t.Errorf("TestHtmlExtraction %v", err)
50 | }
51 |
52 | if !strings.EqualFold(val.(string), expected) {
53 | t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val)
54 | }
55 | }
56 |
57 | func TestHtmlExtraction_PathNotFound(t *testing.T) {
58 | expected := "XML Title"
59 | xmlSource := fmt.Sprintf(`
60 |
61 |
62 | %s
63 | another node
64 | My first paragraph.
65 |
66 | `, expected)
67 |
68 | xe := htmlExtractor{}
69 | xpath := "//h2"
70 | _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)
71 |
72 | if err == nil {
73 | t.Errorf("TestHtmlExtraction_PathNotFound, should be err, got :%v", err)
74 | }
75 | }
76 |
77 | func TestInvalidHtml(t *testing.T) {
78 | xmlSource := `invalid html source`
79 |
80 | xe := htmlExtractor{}
81 | xpath := "//input"
82 | _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)
83 |
84 | if err == nil {
85 | t.Errorf("TestInvalidXml, should be err, got :%v", err)
86 | }
87 | }
88 |
89 | func TestHtmlComplexExtraction(t *testing.T) {
90 | expected := "Html Title"
91 | HtmlSource := fmt.Sprintf(`
92 |
93 |
94 |
104 | %s
105 | My first paragraph.
106 |
107 | `, expected)
108 |
109 | xe := htmlExtractor{}
110 | xpath := "//body/h1"
111 | val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)
112 |
113 | if err != nil {
114 | t.Errorf("TestHtmlExtraction %v", err)
115 | }
116 |
117 | if !strings.EqualFold(val.(string), expected) {
118 | t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val)
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/ddosify_engine/core/types/error.go:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * Ddosify - Load testing tool for any web system.
4 | * Copyright (C) 2021 Ddosify (https://ddosify.com)
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published
8 | * by the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | package types
22 |
23 | import "fmt"
24 |
25 | // Constants for custom error types and reasons
26 | const (
27 | // Types
28 | ErrorProxy = "proxyError"
29 | ErrorConn = "connectionError"
30 | ErrorUnkown = "unknownError"
31 | ErrorIntented = "intentedError" // Errors for created intentionally
32 | ErrorDns = "dnsError"
33 | ErrorParse = "parseError"
34 | ErrorAddr = "addressError"
35 | ErrorInvalidRequest = "invalidRequestError"
36 |
37 | // Reasons
38 | ReasonProxyFailed = "proxy connection refused"
39 | ReasonProxyTimeout = "proxy timeout"
40 | ReasonConnTimeout = "connection timeout"
41 | ReasonReadTimeout = "read timeout"
42 | ReasonConnRefused = "connection refused"
43 |
44 | // In gracefully stop, engine cancels the ongoing requests.
45 | // We can detect the canceled requests with the help of this.
46 | ReasonCtxCanceled = "context canceled"
47 | )
48 |
49 | // RequestError is our custom error struct created in the requester.Requester implementations.
50 | type RequestError struct {
51 | Type string
52 | Reason string
53 | }
54 |
55 | // Custom error message method of ScenarioError
56 | func (e *RequestError) Error() string {
57 | return fmt.Sprintf("%s: %s", e.Type, e.Reason)
58 | }
59 |
60 | type ScenarioValidationError struct { // UnWrappable
61 | msg string
62 | wrappedErr error
63 | }
64 |
65 | func (sc ScenarioValidationError) Error() string {
66 | return sc.msg
67 | }
68 |
69 | func (sc ScenarioValidationError) Unwrap() error {
70 | return sc.wrappedErr
71 | }
72 |
73 | type EnvironmentNotDefinedError struct { // UnWrappable
74 | msg string
75 | wrappedErr error
76 | }
77 |
78 | func (sc EnvironmentNotDefinedError) Error() string {
79 | return sc.msg
80 | }
81 |
82 | func (sc EnvironmentNotDefinedError) Unwrap() error {
83 | return sc.wrappedErr
84 | }
85 |
86 | type CaptureConfigError struct { // UnWrappable
87 | msg string
88 | wrappedErr error
89 | }
90 |
91 | func (sc CaptureConfigError) Error() string {
92 | return sc.msg
93 | }
94 |
95 | func (sc CaptureConfigError) Unwrap() error {
96 | return sc.wrappedErr
97 | }
98 |
99 | type FailedAssertion struct {
100 | Rule string
101 | Received map[string]interface{}
102 | Reason string
103 | }
104 |
--------------------------------------------------------------------------------
/ddosify_engine/config/config_testdata/test_img.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
60 |
--------------------------------------------------------------------------------
/selfhosted/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | echo "⚡ Installing Anteon Self Hosted..."
6 |
7 | echo "🔍 Checking prerequisites..."
8 |
9 | # Function to check if a port is available
10 | is_port_available() {
11 | local port="$1"
12 |
13 | if ! command -v lsof >/dev/null 2>&1; then
14 | echo "❌ lsof not found. Please install lsof and try again."
15 | exit 1
16 | fi
17 |
18 | if lsof -i :"$port" >/dev/null 2>&1; then
19 | echo "❌ Port $port is already in use. Free up the current port and try again."
20 | exit 1
21 | fi
22 | }
23 |
24 | is_port_available 8014
25 | is_port_available 9901
26 | is_port_available 6672
27 | is_port_available 9086
28 | is_port_available 8333
29 |
30 | # Check if Git is installed
31 | if ! command -v git >/dev/null 2>&1; then
32 | echo "❌ Git not found. Please install Git and try again."
33 | exit 1
34 | fi
35 |
36 | # Check if Docker is installed
37 | if ! command -v docker >/dev/null 2>&1; then
38 | echo "❌ Docker not found. Please install Docker and try again."
39 | exit 1
40 | fi
41 |
42 | # Check if Docker Compose is installed
43 | if ! command -v docker-compose >/dev/null 2>&1; then
44 | if ! docker compose version >/dev/null 2>&1; then
45 | echo "❌ Docker Compose not found. Please install Docker Compose and try again."
46 | exit 1
47 | fi
48 | fi
49 |
50 | # Check if Docker is running
51 | if ! docker info >/dev/null 2>&1; then
52 | echo "❌ Docker is not running. Please start Docker and try again."
53 | exit 1
54 | fi
55 |
56 | echo "🚀 Starting installation of Anteon Self Hosted..."
57 |
58 | REPO_DIR="$HOME/.anteon"
59 |
60 | # Check if repository already exists
61 | if [ -d "$REPO_DIR" ]; then
62 | echo "🔄 Repository already exists at $REPO_DIR - Attempting to update..."
63 | cd "$REPO_DIR"
64 | git checkout master
65 | cd "$REPO_DIR/selfhosted"
66 | git pull 2>&1 || {
67 | read -p "⚠️ Error updating repository. Clean and update? [Y/n]: " answer
68 | answer=${answer:-Y}
69 | if [[ $answer =~ ^[Yy]$ ]]; then
70 | git reset --hard >/dev/null 2>&1
71 | git clean -fd >/dev/null 2>&1
72 | git pull >/dev/null 2>&1
73 | fi
74 | }
75 | else
76 | # Clone the repository
77 | echo "📦 Cloning repository to $REPO_DIR directory..."
78 | git clone https://github.com/getanteon/anteon.git "$REPO_DIR" >/dev/null 2>&1
79 | cd "$REPO_DIR"
80 | git checkout master >/dev/null 2>&1
81 | cd "$REPO_DIR/selfhosted"
82 | fi
83 |
84 | # Determine which compose command to use
85 | COMPOSE_COMMAND="docker-compose"
86 | if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
87 | COMPOSE_COMMAND="docker compose"
88 | fi
89 |
90 | echo "🚀 Deploying Anteon Self Hosted..."
91 | $COMPOSE_COMMAND -f "$REPO_DIR/selfhosted/docker-compose.yml" up -d
92 | docker pull busybox:1.34.1 >/dev/null 2>&1
93 | echo ""
94 | echo "⏳ Waiting for services to be ready..."
95 | docker run --rm --network selfhosted_anteon busybox:1.34.1 /bin/sh -c "until nc -z nginx 80 && nc -z backend 8008 && nc -z hammermanager 8001 && nc -z rabbitmq 5672 && nc -z postgres 5432 && nc -z influxdb 8086 && nc -z seaweedfs 8333; do sleep 5; done"
96 | echo "✅ Anteon Self Hosted installation complete!"
97 | echo "📁 Installation directory: $REPO_DIR/selfhosted"
98 | echo "🔥 To remove Anteon Self Hosted, run: cd $REPO_DIR/selfhosted && $COMPOSE_COMMAND down"
99 | echo ""
100 | echo "🌐 Open http://localhost:8014 in your browser to access the application."
101 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/client_pool.go:
--------------------------------------------------------------------------------
1 | package scenario
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "net/http/cookiejar"
7 | "net/url"
8 |
9 | "go.ddosify.com/ddosify/core/types"
10 | "go.ddosify.com/ddosify/core/util"
11 | )
12 |
13 | // Factory is a function to create new connections.
14 | type ClientFactoryMethod func() *http.Client
15 | type ClientCloseMethod func(*http.Client)
16 |
17 | // NewClientPool returns a new pool based on buffered channels with an initial
18 | // capacity and maximum capacity. Factory is used when initial capacity is
19 | // greater than zero to fill the pool. A zero initialCap doesn't fill the Pool
20 | // until a new Get() is called. During a Get(), If there is no new client
21 | // available in the pool, a new client will be created via the Factory()
22 | // method.
23 | func NewClientPool(initialCap, maxCap int, engineMode string, factory ClientFactoryMethod, close ClientCloseMethod) (*util.Pool[*http.Client], error) {
24 | if initialCap < 0 || maxCap <= 0 || initialCap > maxCap {
25 | return nil, errors.New("invalid capacity settings")
26 | }
27 |
28 | pool := &util.Pool[*http.Client]{
29 | Items: make(chan *http.Client, maxCap),
30 | Factory: factory,
31 | Close: close,
32 | AfterPut: func(client *http.Client) {
33 | // if engine is in repeated mode, notify jar that cookies are already set
34 | // to avoid setting them again in the next iteration
35 | if engineMode == types.EngineModeRepeatedUser && client.Jar != nil && !client.Jar.(*cookieJarRepeated).firstIterPassed {
36 | client.Jar.(*cookieJarRepeated).firstIterPassed = true
37 | }
38 | },
39 | }
40 |
41 | // create initial clients, if something goes wrong,
42 | // just close the pool error out.
43 | for i := 0; i < initialCap; i++ {
44 | client := pool.Factory()
45 | pool.Items <- client
46 | }
47 |
48 | return pool, nil
49 | }
50 |
51 | type cookieJarRepeated struct {
52 | defaultCookieJar *cookiejar.Jar
53 | firstIterPassed bool
54 | }
55 |
56 | func NewCookieJarRepeated() (*cookieJarRepeated, error) {
57 | jar, err := cookiejar.New(nil)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return &cookieJarRepeated{defaultCookieJar: jar}, nil
62 | }
63 |
64 | // SetCookies implements the http.CookieJar interface.
65 | // Only set cookies if they are not already set for repeated mode.
66 | func (c *cookieJarRepeated) SetCookies(u *url.URL, cookies []*http.Cookie) {
67 | if !c.firstIterPassed {
68 | // execute default behavior if no cookies are set
69 | c.defaultCookieJar.SetCookies(u, cookies)
70 | }
71 | }
72 |
73 | // Cookies implements the http.CookieJar interface.
74 | func (c *cookieJarRepeated) Cookies(u *url.URL) []*http.Cookie {
75 | return c.defaultCookieJar.Cookies(u)
76 | }
77 |
78 | var defaultFactory = func() *http.Client {
79 | return &http.Client{}
80 | }
81 |
82 | var defaultClose = func(c *http.Client) {
83 | c.CloseIdleConnections()
84 | }
85 |
86 | // createClientFactoryMethod returns a Factory function based on the engine mode.
87 | func createClientFactoryMethod(mode string, opts ...func(http.CookieJar)) ClientFactoryMethod {
88 | if mode == types.EngineModeRepeatedUser {
89 | return func() *http.Client {
90 | jar, err := NewCookieJarRepeated()
91 | if err != nil {
92 | return defaultFactory() // no cookie jar, use default factory
93 | }
94 |
95 | for _, opt := range opts {
96 | opt(jar)
97 | }
98 | return &http.Client{Jar: jar}
99 | }
100 | }
101 |
102 | // distinct users mode
103 | return func() *http.Client {
104 | jar, err := cookiejar.New(nil)
105 | if err != nil {
106 | return defaultFactory() // no cookie jar, use default factory
107 | }
108 |
109 | for _, opt := range opts {
110 | opt(jar)
111 | }
112 | return &http.Client{Jar: jar}
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Anteon 🐝
2 |
3 | Thank you for your interest in contributing to [Anteon](https://github.com/getanteon/anteon)!
4 |
5 | In this guide, we'll provide you with the necessary information and guidelines to help you get started.
6 |
7 | ## 🚀 Getting Started
8 |
9 | 1. Fork [Anteon](https://github.com/getanteon/anteon) on GitHub.
10 | 2. Clone your fork to your local machine:
11 |
12 | ```bash
13 | git clone git@github.com:/anteon.git
14 | ```
15 |
16 | 3. Add the Anteon repository as an upstream remote:
17 |
18 | ```bash
19 | git remote add upstream https://github.com/getanteon/anteon
20 | ```
21 |
22 | 4. We follow Gitflow branching model. Create a feature branch from the `develop` branch:
23 |
24 | ```bash
25 | git checkout -b feature/FEATURE_NAME develop
26 | ```
27 |
28 | 5. Set up your development environment.
29 |
30 | - Go programming language (`Version >= 1.18`) is required to build and run Anteon. You can find the installation instructions [here](https://go.dev/doc/install).
31 |
32 | - We also provide [Dockerfile](./.devcontainer/Dockerfile.dev) and Visual Studio Code (VS Code) [remote container configuration](./.devcontainer/devcontainer.json) for development. More information about VS Code remote container can be found [here](https://code.visualstudio.com/docs/devcontainers/containers).
33 |
34 | 6. Run the `main.go` file:
35 |
36 | ```bash
37 | go run main.go
38 | ```
39 |
40 | ## 💻 Submitting Changes
41 |
42 | Before submitting a [pull request (PR)](https://github.com/getanteon/anteon/pulls) with your changes, please make sure you follow these guidelines:
43 |
44 | 1. Ensure your code is well-formatted and follows the established coding style for this project (e.g., proper indentation, naming conventions, etc.).
45 | 2. Write unit tests for any new functionality or bug fixes. Ensure that all tests pass before submitting your PR.
46 | 3. Update the [README.md](./README.md) file according to your changes.
47 |
48 | 4. Keep your PRs focused and as small as possible. If you have multiple unrelated changes, create separate PRs for them.
49 |
50 | 5. Add a descriptive title and detailed description to your PR, explaining the purpose and rationale behind your changes.
51 |
52 | 6. Rebase your branch with the latest upstream changes before submitting your PR:
53 |
54 | ```bash
55 | git pull --rebase upstream master
56 | ```
57 |
58 | 7. Create a pull request (PR) against the `develop` branch.
59 |
60 | After submitting your PR, our team will review your changes. We may ask for revisions or provide feedback before merging your changes into the master branch. Your patience and cooperation are greatly appreciated.
61 |
62 | ## 🐛 Bug Reports
63 |
64 | When submitting a [bug report](https://github.com/getanteon/anteon/issues), please include:
65 |
66 | - A clear and descriptive title.
67 | - A detailed description of the issue, including the steps to reproduce the bug.
68 | - Any relevant information about your environment, such as the OS, Go version, and configuration.
69 | - If possible, attach a minimal code sample or test case that demonstrates the issue.
70 | - If possible, attach a screenshot or animated GIF that demonstrates the issue.
71 |
72 | ## ✨ Feature Requests
73 |
74 | When submitting a [feature request](https://github.com/getanteon/anteon/issues), please include:
75 |
76 | - A clear and descriptive title.
77 | - A detailed description of the proposed feature or enhancement, including the rationale behind it and any potential use cases.
78 | - If possible, provide examples or mockups to help illustrate your proposal.
79 |
80 | ## 💬 Community
81 |
82 | Join our [Discord Server](https://discord.com/invite/9KdnrSUZQg) for issues, feature requests, feedbacks or anything else. We're happy to help you out!
83 |
84 | ## 📜 Code of Conduct
85 |
86 | By participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). Please read it carefully and ensure that your contributions and interactions with the community adhere to its principles.
87 |
--------------------------------------------------------------------------------
/ddosify_engine/core/assertion/service_test.go:
--------------------------------------------------------------------------------
1 | package assertion
2 |
3 | import (
4 | "reflect"
5 | "sort"
6 | "sync"
7 | "testing"
8 | "time"
9 |
10 | "go.ddosify.com/ddosify/core/types"
11 | )
12 |
13 | func TestApplyAssertionsAbortsCorrectly(t *testing.T) {
14 | service := NewDefaultAssertionService()
15 | assertions := make(map[string]types.TestAssertionOpt)
16 | rule := "false"
17 | delay := 3
18 | assertions[rule] = types.TestAssertionOpt{
19 | Abort: true,
20 | Delay: delay,
21 | }
22 | abortChan := service.Init(assertions)
23 |
24 | inputChan := make(chan *types.ScenarioResult)
25 | go service.Start(inputChan)
26 |
27 | wg := sync.WaitGroup{}
28 | wg.Add(1)
29 | go func() {
30 | <-abortChan
31 | wg.Done()
32 | }()
33 |
34 | inputChan <- &types.ScenarioResult{}
35 | start := time.Now()
36 |
37 | wg.Wait()
38 | timePassed := time.Since(start).Seconds()
39 | if int(timePassed) != delay {
40 | t.Errorf("Delay, got %f, expected %d", timePassed, delay)
41 | }
42 | }
43 |
44 | func TestServiceKeepsIterationTimes(t *testing.T) {
45 | service := NewDefaultAssertionService()
46 | assertions := make(map[string]types.TestAssertionOpt)
47 | rule := "false"
48 | delay := 3
49 | assertions[rule] = types.TestAssertionOpt{
50 | Abort: false,
51 | Delay: delay,
52 | }
53 | _ = service.Init(assertions)
54 |
55 | inputChan := make(chan *types.ScenarioResult)
56 | go service.Start(inputChan)
57 |
58 | wg := sync.WaitGroup{}
59 | wg.Add(1)
60 | go func() {
61 | <-service.ResultChan()
62 | wg.Done()
63 | }()
64 |
65 | expectedIterationTimes := SortableInt64Slice{}
66 | for i := 0; i < 10; i++ {
67 | iterTime := time.Duration(((i * 5) % 4) * int(time.Millisecond))
68 | expectedIterationTimes = append(expectedIterationTimes, iterTime.Milliseconds())
69 | inputChan <- &types.ScenarioResult{
70 | StepResults: []*types.ScenarioStepResult{
71 | {
72 | StepID: 1,
73 | Duration: iterTime,
74 | },
75 | },
76 | }
77 | }
78 | sort.Sort(expectedIterationTimes)
79 | close(inputChan)
80 |
81 | wg.Wait()
82 |
83 | iterationTimes := service.GetTotalTimes()
84 |
85 | if !reflect.DeepEqual(iterationTimes, []int64(expectedIterationTimes)) {
86 | t.Errorf("TestServiceKeepsIterationTimes, cumulative data store failed")
87 | }
88 | }
89 |
90 | func TestServiceKeepsFailCount(t *testing.T) {
91 | service := NewDefaultAssertionService()
92 | assertions := make(map[string]types.TestAssertionOpt)
93 | _ = service.Init(assertions)
94 |
95 | inputChan := make(chan *types.ScenarioResult)
96 | go service.Start(inputChan)
97 |
98 | wg := sync.WaitGroup{}
99 | wg.Add(1)
100 | go func() {
101 | <-service.ResultChan()
102 | wg.Done()
103 | }()
104 |
105 | N := 10
106 | // 2*N times failed iteration result
107 | for i := 0; i < N; i++ {
108 | inputChan <- &types.ScenarioResult{
109 | StepResults: []*types.ScenarioStepResult{
110 | {
111 | StepID: 1,
112 | FailedAssertions: []types.FailedAssertion{
113 | {
114 | Rule: "failed assertion expression",
115 | Received: map[string]interface{}{},
116 | Reason: "",
117 | },
118 | },
119 | },
120 | },
121 | }
122 | inputChan <- &types.ScenarioResult{
123 | StepResults: []*types.ScenarioStepResult{
124 | {
125 | StepID: 1,
126 | Err: types.RequestError{
127 | Type: "server error type",
128 | Reason: "",
129 | },
130 | },
131 | },
132 | }
133 | }
134 | close(inputChan)
135 |
136 | wg.Wait()
137 |
138 | failCount := service.GetFailCount()
139 |
140 | if failCount != 2*N {
141 | t.Errorf("TestServiceKeepsFailCount, expected : %d, got : %d", 2*N, failCount)
142 | }
143 | }
144 |
145 | type SortableInt64Slice []int64
146 |
147 | func (a SortableInt64Slice) Len() int { return len(a) }
148 | func (a SortableInt64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
149 | func (a SortableInt64Slice) Less(i, j int) bool { return a[i] < a[j] }
150 |
--------------------------------------------------------------------------------
/ddosify_engine/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | project_name: ddosify
2 | before:
3 | hooks:
4 | - go mod tidy
5 | builds:
6 | - env:
7 | - CGO_ENABLED=0
8 | goos:
9 | - linux
10 | - windows
11 | - darwin
12 | goarch:
13 | - 386
14 | - amd64
15 | - arm
16 | - arm64
17 | goarm:
18 | - 6
19 | ldflags:
20 | - -s -w -X main.GitVersion={{ .Tag }} -X main.GitCommit={{ .ShortCommit }} -X main.BuildDate={{ .CommitDate }}
21 | ignore:
22 | - goos: darwin
23 | goarch: 386
24 | - goos: darwin
25 | goarch: arm
26 | goarm: 7
27 | - goos: darwin
28 | goarch: arm
29 | goarm: 6
30 | - goos: darwin
31 | goarch: arm
32 | goarm: 5
33 | archives:
34 | - format_overrides:
35 | - goos: windows
36 | format: zip
37 | files:
38 | - README.md
39 | - LICENSE*
40 |
41 | universal_binaries:
42 | - replace: true
43 |
44 | checksum:
45 | name_template: 'checksums.txt'
46 | snapshot:
47 | name_template: "{{ incpatch .Tag }}-next"
48 | changelog:
49 | sort: asc
50 | use: github
51 | filters:
52 | exclude:
53 | - '^docs:'
54 | - '^test:'
55 | - Merge pull request
56 | - Merge branch
57 | - go mod tidy
58 |
59 | brews:
60 | - tap:
61 | owner: ddosify
62 | name: homebrew-tap
63 | folder: Formula
64 | homepage: https://ddosify.com
65 | description: High-performance load testing tool, written in Golang.
66 | license: AGPL-3.0-only
67 | skip_upload: false
68 | test: |
69 | system "#{bin}/ddosify --help"
70 | dependencies:
71 | - name: go
72 | type: optional
73 | install: |-
74 | bin.install "ddosify"
75 | commit_author:
76 | name: ddosifyadmin
77 | email: admin@ddosify.com
78 |
79 | dockers:
80 | - image_templates:
81 | - 'ddosify/ddosify:{{ .Tag }}-amd64'
82 | dockerfile: Dockerfile.release
83 | use: buildx
84 | build_flag_templates:
85 | - "--pull"
86 | - "--label=org.opencontainers.image.created={{.Date}}"
87 | - "--label=org.opencontainers.image.name={{.ProjectName}}"
88 | - "--label=org.opencontainers.image.revision={{.FullCommit}}"
89 | - "--label=org.opencontainers.image.version={{.Tag}}"
90 | - "--label=org.opencontainers.image.source={{.GitURL}}"
91 | - "--platform=linux/amd64"
92 | extra_files:
93 | - assets/ddosify.profile
94 | - image_templates:
95 | - 'ddosify/ddosify:{{ .Tag }}-arm64'
96 | dockerfile: Dockerfile.release
97 | use: buildx
98 | build_flag_templates:
99 | - "--pull"
100 | - "--label=org.opencontainers.image.created={{.Date}}"
101 | - "--label=org.opencontainers.image.name={{.ProjectName}}"
102 | - "--label=org.opencontainers.image.revision={{.FullCommit}}"
103 | - "--label=org.opencontainers.image.version={{.Tag}}"
104 | - "--label=org.opencontainers.image.source={{.GitURL}}"
105 | - "--platform=linux/arm64"
106 | extra_files:
107 | - assets/ddosify.profile
108 | goarch: arm64
109 |
110 | docker_manifests:
111 | - name_template: 'ddosify/ddosify:{{ .Tag }}'
112 | image_templates:
113 | - 'ddosify/ddosify:{{ .Tag }}-amd64'
114 | - 'ddosify/ddosify:{{ .Tag }}-arm64'
115 | - name_template: 'ddosify/ddosify:latest'
116 | image_templates:
117 | - 'ddosify/ddosify:{{ .Tag }}-amd64'
118 | - 'ddosify/ddosify:{{ .Tag }}-arm64'
119 |
120 | nfpms:
121 | - file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
122 | id: packages
123 | homepage: https://ddosify.com
124 | description: High-performance load testing tool, written in Golang.
125 | maintainer: Ddosify
126 | license: AGPL-3.0-only
127 | vendor: Ddosify
128 | formats:
129 | - apk
130 | - deb
131 | - rpm
132 |
133 | release:
134 | footer: |
135 | ## More? 🚀
136 |
137 | - Join our [Discord server](https://discord.com/invite/9KdnrSUZQg)
138 | - Follow us on [Twitter](https://twitter.com/getanteon)
139 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/extraction/base.go:
--------------------------------------------------------------------------------
1 | package extraction
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 |
8 | "go.ddosify.com/ddosify/core/types"
9 | )
10 |
11 | func Extract(source interface{}, ce types.EnvCaptureConf) (val interface{}, err error) {
12 | defer func() {
13 | if r := recover(); r != nil {
14 | switch x := r.(type) {
15 | case string:
16 | err = errors.New(x)
17 | case error:
18 | err = x
19 | default:
20 | err = errors.New("Unknown panic")
21 | }
22 | val = nil
23 | }
24 | }()
25 |
26 | if source == nil {
27 | return "", ExtractionError{
28 | msg: "source is nil",
29 | }
30 | }
31 |
32 | switch ce.From {
33 | case types.Header:
34 | header := source.(http.Header)
35 | if ce.Key != nil { // key specified
36 | val = header.Get(*ce.Key)
37 | if val == "" {
38 | err = fmt.Errorf("http header %s not found", *ce.Key)
39 | } else if ce.RegExp != nil { // run regex for found value
40 | val, err = ExtractWithRegex(val, *ce.RegExp)
41 | }
42 | } else {
43 | err = fmt.Errorf("http header key not specified")
44 | }
45 | case types.Body:
46 | if ce.JsonPath != nil {
47 | val, err = ExtractFromJson(source, *ce.JsonPath)
48 | } else if ce.RegExp != nil {
49 | val, err = ExtractWithRegex(source, *ce.RegExp)
50 | } else if ce.Xpath != nil {
51 | val, err = ExtractFromXml(source, *ce.Xpath)
52 | } else if ce.XpathHtml != nil {
53 | val, err = ExtractFromHtml(source, *ce.XpathHtml)
54 | }
55 | case types.Cookie:
56 | cookies := source.(map[string]*http.Cookie)
57 | if ce.CookieName != nil { // cookie name specified
58 | c, ok := cookies[*ce.CookieName]
59 | if !ok {
60 | err = fmt.Errorf("cookie %s not found", *ce.CookieName)
61 | } else {
62 | val = c.Value
63 | }
64 | } else {
65 | err = fmt.Errorf("cookie name not specified")
66 | }
67 | }
68 |
69 | if err != nil {
70 | return "", ExtractionError{
71 | msg: fmt.Sprintf("%v", err),
72 | wrappedErr: err,
73 | }
74 | }
75 | return val, nil
76 |
77 | }
78 |
79 | func ExtractWithRegex(source interface{}, regexConf types.RegexCaptureConf) (val interface{}, err error) {
80 | re := regexExtractor{}
81 | re.Init(*regexConf.Exp)
82 | switch s := source.(type) {
83 | case []byte: // from response body
84 | return re.extractFromByteSlice(s, regexConf.No)
85 | case string: // from response header
86 | return re.extractFromString(s, regexConf.No)
87 | default:
88 | return "", fmt.Errorf("Unsupported type for extraction source")
89 | }
90 | }
91 |
92 | func ExtractFromJson(source interface{}, jsonPath string) (interface{}, error) {
93 | je := jsonExtractor{}
94 | switch s := source.(type) {
95 | case []byte: // from response body
96 | return je.extractFromByteSlice(s, jsonPath)
97 | case string: // from response header
98 | return je.extractFromString(s, jsonPath)
99 | default:
100 | return "", fmt.Errorf("Unsupported type for extraction source")
101 | }
102 | }
103 |
104 | func ExtractFromXml(source interface{}, xPath string) (interface{}, error) {
105 | xe := xmlExtractor{}
106 | switch s := source.(type) {
107 | case []byte: // from response body
108 | return xe.extractFromByteSlice(s, xPath)
109 | case string: // from response header
110 | return xe.extractFromString(s, xPath)
111 | default:
112 | return "", fmt.Errorf("Unsupported type for extraction source")
113 | }
114 | }
115 |
116 | func ExtractFromHtml(source interface{}, xPath string) (interface{}, error) {
117 | xe := htmlExtractor{}
118 | switch s := source.(type) {
119 | case []byte: // from response body
120 | return xe.extractFromByteSlice(s, xPath)
121 | case string: // from response header
122 | return xe.extractFromString(s, xPath)
123 | default:
124 | return "", fmt.Errorf("Unsupported type for extraction source")
125 | }
126 | }
127 |
128 | type ExtractionError struct { // UnWrappable
129 | msg string
130 | wrappedErr error
131 | }
132 |
133 | func (sc ExtractionError) Error() string {
134 | return sc.msg
135 | }
136 |
137 | func (sc ExtractionError) Unwrap() error {
138 | return sc.wrappedErr
139 | }
140 |
--------------------------------------------------------------------------------
/ddosify_engine/core/report/debug.go:
--------------------------------------------------------------------------------
1 | package report
2 |
3 | import (
4 | "encoding/json"
5 | "html"
6 | "net/http"
7 | "strings"
8 |
9 | "go.ddosify.com/ddosify/core/types"
10 | )
11 |
12 | type verboseRequest struct {
13 | Url string `json:"url"`
14 | Method string `json:"method"`
15 | Headers map[string]string `json:"headers"`
16 | Body interface{} `json:"body"`
17 | }
18 |
19 | type verboseResponse struct {
20 | StatusCode int `json:"status_code"`
21 | Headers map[string]string `json:"headers"`
22 | Body interface{} `json:"body"`
23 | ResponseTime int64 `json:"response_time"` // in milliseconds
24 | }
25 |
26 | type verboseHttpRequestInfo struct {
27 | StepId uint16 `json:"step_id"`
28 | StepName string `json:"step_name"`
29 | Request verboseRequest `json:"request"`
30 | Response verboseResponse `json:"response"`
31 | Envs map[string]interface{} `json:"envs"`
32 | TestData map[string]interface{} `json:"test_data"`
33 | FailedCaptures map[string]string `json:"failed_captures"`
34 | FailedAssertions []types.FailedAssertion `json:"failed_assertions"`
35 | Error string `json:"error"`
36 | }
37 |
38 | func ScenarioStepResultToVerboseHttpRequestInfo(sr *types.ScenarioStepResult) verboseHttpRequestInfo {
39 | var verboseInfo verboseHttpRequestInfo
40 |
41 | verboseInfo.StepId = sr.StepID
42 | verboseInfo.StepName = sr.StepName
43 |
44 | if sr.Err.Type == types.ErrorInvalidRequest {
45 | // could not prepare request at all
46 | verboseInfo.Error = sr.Err.Error()
47 | return verboseInfo
48 | }
49 |
50 | requestHeaders, requestBody, _ := decode(sr.ReqHeaders,
51 | sr.ReqBody)
52 | verboseInfo.Request = struct {
53 | Url string "json:\"url\""
54 | Method string "json:\"method\""
55 | Headers map[string]string "json:\"headers\""
56 | Body interface{} "json:\"body\""
57 | }{
58 | Url: sr.Url,
59 | Method: sr.Method,
60 | Headers: requestHeaders,
61 | Body: requestBody,
62 | }
63 |
64 | if sr.Err.Type != "" {
65 | verboseInfo.Error = sr.Err.Error()
66 | } else {
67 | responseHeaders, responseBody, _ := decode(sr.RespHeaders,
68 | sr.RespBody)
69 | // TODO what to do with error
70 | verboseInfo.Response = verboseResponse{
71 | StatusCode: sr.StatusCode,
72 | Headers: responseHeaders,
73 | Body: responseBody,
74 | ResponseTime: sr.Duration.Milliseconds(),
75 | }
76 | }
77 |
78 | envs := make(map[string]interface{})
79 | testData := make(map[string]interface{})
80 | for key, val := range sr.UsableEnvs {
81 | if strings.HasPrefix(key, "data.") {
82 | testData[key] = val
83 | } else {
84 | envs[key] = val
85 | }
86 | }
87 |
88 | verboseInfo.Envs = envs
89 | verboseInfo.TestData = testData
90 | verboseInfo.FailedCaptures = sr.FailedCaptures
91 | verboseInfo.FailedAssertions = sr.FailedAssertions
92 |
93 | return verboseInfo
94 | }
95 |
96 | func decode(headers http.Header, byteBody []byte) (map[string]string, interface{}, error) {
97 | contentType := headers.Get("Content-Type")
98 | var reqBody interface{}
99 |
100 | hs := make(map[string]string, 0)
101 | for k, v := range headers {
102 | values := strings.Join(v, ",")
103 | hs[k] = values
104 | }
105 |
106 | if strings.Contains(contentType, "text/html") {
107 | unescapedHmtl := html.UnescapeString(string(byteBody))
108 | reqBody = unescapedHmtl
109 | } else if strings.Contains(contentType, "application/json") {
110 | err := json.Unmarshal(byteBody, &reqBody)
111 | if err != nil {
112 | reqBody = string(byteBody)
113 | }
114 | } else { // for remaining content-types return plain string
115 | // xml.Unmarshal() needs xml tags to decode encoded xml, we have no knowledge about the xml structure
116 | reqBody = string(byteBody)
117 | }
118 |
119 | return hs, reqBody, nil
120 | }
121 |
122 | func isVerboseInfoRequestEmpty(req verboseRequest) bool {
123 | if req.Url == "" && req.Method == "" && req.Headers == nil && req.Body == nil {
124 | return true
125 | }
126 | return false
127 | }
128 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/data/csv.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "encoding/csv"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "net/url"
10 | "os"
11 | "strconv"
12 |
13 | "go.ddosify.com/ddosify/core/types"
14 | )
15 |
16 | func validateConf(conf types.CsvConf) error {
17 | if !(conf.Order == "random" || conf.Order == "sequential") {
18 | return fmt.Errorf("unsupported order %s, should be random|sequential", conf.Order)
19 | }
20 | return nil
21 | }
22 |
23 | type RemoteCsvError struct { // UnWrappable
24 | msg string
25 | wrappedErr error
26 | }
27 |
28 | func (nf RemoteCsvError) Error() string {
29 | if nf.wrappedErr != nil {
30 | return fmt.Sprintf("%s,%s", nf.msg, nf.wrappedErr.Error())
31 | }
32 | return nf.msg
33 | }
34 |
35 | func (nf RemoteCsvError) Unwrap() error {
36 | return nf.wrappedErr
37 | }
38 |
39 | func ReadCsv(conf types.CsvConf) ([]map[string]interface{}, error) {
40 | err := validateConf(conf)
41 | if err != nil {
42 | return nil, err
43 | }
44 |
45 | var reader io.Reader
46 |
47 | var pUrl *url.URL
48 | if pUrl, err = url.ParseRequestURI(conf.Path); err == nil && pUrl.IsAbs() { // url
49 | req, err := http.NewRequest(http.MethodGet, conf.Path, nil)
50 | if err != nil {
51 | return nil, wrapAsCsvError("can not create request", err)
52 | }
53 | resp, err := http.DefaultClient.Do(req)
54 | if err != nil {
55 | return nil, wrapAsCsvError("can not get response", err)
56 | }
57 |
58 | if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
59 | return nil, wrapAsCsvError(fmt.Sprintf("Test Data: request to remote url (%s) failed. Status Code: %d", conf.Path, resp.StatusCode), nil)
60 | }
61 | reader = resp.Body
62 | defer resp.Body.Close()
63 | } else if _, err = os.Stat(conf.Path); err == nil { // local file path
64 | f, err := os.Open(conf.Path)
65 | if err != nil {
66 | return nil, err
67 | }
68 | reader = f
69 | defer f.Close()
70 | } else {
71 | return nil, wrapAsCsvError(fmt.Sprintf("can not parse path: %s", conf.Path), err)
72 | }
73 |
74 | // read csv values using csv.Reader
75 | csvReader := csv.NewReader(reader)
76 | csvReader.Comma = []rune(conf.Delimiter)[0]
77 | csvReader.TrimLeadingSpace = true
78 | csvReader.LazyQuotes = conf.AllowQuota
79 |
80 | data, err := csvReader.ReadAll()
81 | if err != nil {
82 | return nil, err
83 | }
84 |
85 | if conf.SkipFirstLine {
86 | data = data[1:]
87 | }
88 |
89 | rt := make([]map[string]interface{}, 0) // unclear how many empty line exist
90 |
91 | for _, row := range data {
92 | if conf.SkipEmptyLine && emptyLine(row) {
93 | continue
94 | }
95 | x := map[string]interface{}{}
96 | for index, tag := range conf.Vars {
97 | i, err := strconv.Atoi(index)
98 | if err != nil {
99 | return nil, err
100 | }
101 |
102 | if i >= len(row) {
103 | return nil, fmt.Errorf("index number out of range, check your vars or delimiter")
104 | }
105 |
106 | // convert
107 | var val interface{}
108 | switch tag.Type {
109 | case "json":
110 | err := json.Unmarshal([]byte(row[i]), &val)
111 | if err != nil {
112 | return nil, fmt.Errorf("can not convert %s to json,%v", row[i], err)
113 | }
114 | case "int":
115 | var err error
116 | val, err = strconv.Atoi(row[i])
117 | if err != nil {
118 | return nil, fmt.Errorf("can not convert %s to int,%v", row[i], err)
119 | }
120 | case "float":
121 | var err error
122 | val, err = strconv.ParseFloat(row[i], 64)
123 | if err != nil {
124 | return nil, fmt.Errorf("can not convert %s to float,%v", row[i], err)
125 | }
126 | case "bool":
127 | var err error
128 | val, err = strconv.ParseBool(row[i])
129 | if err != nil {
130 | return nil, fmt.Errorf("can not convert %s to bool,%v", row[i], err)
131 | }
132 | default:
133 | val = row[i]
134 | }
135 | x[tag.Tag] = val
136 | }
137 | rt = append(rt, x)
138 | }
139 |
140 | return rt, nil
141 | }
142 |
143 | func emptyLine(row []string) bool {
144 | for _, field := range row {
145 | if field != "" {
146 | return false
147 | }
148 | }
149 | return true
150 | }
151 |
152 | func wrapAsCsvError(msg string, err error) RemoteCsvError {
153 | var csvReqError RemoteCsvError
154 | csvReqError.msg = msg
155 | csvReqError.wrappedErr = err
156 | return csvReqError
157 | }
158 |
--------------------------------------------------------------------------------
/ddosify_engine/config_examples/config.json:
--------------------------------------------------------------------------------
1 | // This file contains full features of Ddosify as a reference. Don't use it directly.
2 | {
3 | "request_count": 30, // This field will be deprecated, please use iteration_count instead.
4 | "iteration_count": 30,
5 | "debug" : false, // use this field for debugging, see verbose result
6 | "load_type": "linear",
7 | "engine_mode": "distinct-user", // could be one of "distinct-user","repeated-user", or default mode "ddosify"
8 | "duration": 5,
9 | "manual_load": [
10 | {"duration": 5, "count": 5},
11 | {"duration": 6, "count": 10},
12 | {"duration": 7, "count": 20}
13 | ],
14 | "env" : {
15 | "HTTPBIN" : "https://httpbin.ddosify.com",
16 | "LOCAL" : "http://localhost:8084",
17 | "NAMES" : ["kenan","fatih","kursat","semih","sertac"] ,
18 | "NUMBERS" : [52,99,60,33],
19 | "BOOLS" : [true,true,true,false],
20 | "randomIntPerIteration": "{{_randomInt}}"
21 | },
22 | "data":{
23 | "info": {
24 | "path" : "config/config_testdata/test.csv",
25 | "delimiter": ";",
26 | "vars": {
27 | "0":{"tag":"name"},
28 | "1":{"tag":"city"},
29 | "2":{"tag":"team"},
30 | "3":{"tag":"payload", "type":"json"},
31 | "4":{"tag":"age", "type":"int"}
32 | },
33 | "allow_quota" : true,
34 | "order": "random",
35 | "skip_first_line" : true,
36 | "skip_empty_line" : true
37 | }
38 | },
39 | "success_criterias": [
40 | {
41 | "rule" : "p90(iteration_duration) < 220",
42 | "abort" : false
43 | },
44 | {
45 | "rule" : "fail_count_perc < 0.1",
46 | "abort" : true,
47 | "delay" : 1
48 | },
49 | {
50 | "rule" : "fail_count < 100",
51 | "abort" : true,
52 | "delay" : 0
53 | }
54 | ],
55 | "proxy": "http://proxy_host.com:proxy_port",
56 | "output": "stdout",
57 | "steps": [
58 | {
59 | "id": 1,
60 | "url": "https://getanteon.com/endpoint_1",
61 | "method": "POST",
62 | "headers": {
63 | "Content-Type": "application/xml",
64 | "header1": "header2"
65 | },
66 | "payload": "Body content 1",
67 | "timeout": 3,
68 | "sleep": "300-500",
69 | "auth": {
70 | "username": "test_user",
71 | "password": "12345"
72 | },
73 | "others": {
74 | "disable-compression": false,
75 | "h2": true,
76 | "disable-redirect": true
77 | },
78 | "capture_env": {
79 | "NUM" :{ "from":"body","json_path":"num"}
80 | },
81 | "assertion":[
82 | "equals(status_code,200)",
83 | "in(variables.num,[10,20])"
84 | ]
85 | },
86 | {
87 | "id": 2,
88 | "url": "{{LOCAL}}",
89 | "method": "GET",
90 | "payload_file": "config_examples/payload.txt",
91 | "timeout": 2,
92 | "sleep": "1000",
93 | "headers":{
94 | "num": "{{NUM}}",
95 | "randNum": "{{rand(NUMBERS)}}",
96 | "randInt" : "{{randomIntPerIteration}}"
97 | },
98 | "assertion":[
99 | "contains(body,\"xyz\")",
100 | "range(headers.content-length,1000,10000)"
101 | ]
102 | },
103 | {
104 | "id": 3,
105 | "url": "https://getanteon.com/endpoint_3",
106 | "method": "POST",
107 | "payload_multipart": [
108 | {
109 | "name": "[field-name]",
110 | "value": "[field-value]"
111 | },
112 | {
113 | "name": "[field-name]",
114 | "value": "./test.png",
115 | "type": "file"
116 | },
117 | {
118 | "name": "[field-name]",
119 | "value": "http://test.com/test.png",
120 | "type": "file",
121 | "src": "remote"
122 | }
123 | ],
124 | "timeout": 2
125 | }
126 | ]
127 | }
128 |
--------------------------------------------------------------------------------
/ddosify_engine/core/types/scenario_test.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "errors"
7 | "net/http"
8 | "testing"
9 | )
10 |
11 | func TestScenarioStepValid_EnvVariableInHeader(t *testing.T) {
12 | url := "https://test.com"
13 | st := ScenarioStep{
14 | ID: 22,
15 | Name: "",
16 | Method: http.MethodGet,
17 | Auth: Auth{},
18 | Cert: tls.Certificate{},
19 | CertPool: &x509.CertPool{},
20 | Headers: map[string]string{
21 | "{{ARGENTINA}}": "{{ARGENTINA}}",
22 | },
23 | Payload: "",
24 | URL: url,
25 | Timeout: 0,
26 | Sleep: "",
27 | Custom: map[string]interface{}{},
28 | EnvsToCapture: []EnvCaptureConf{},
29 | }
30 |
31 | definedEnvs := map[string]struct{}{}
32 | err := st.validate(definedEnvs)
33 |
34 | var environmentNotDefined EnvironmentNotDefinedError
35 |
36 | if !errors.As(err, &environmentNotDefined) {
37 | t.Errorf("Should be EnvironmentNotDefinedError")
38 | }
39 |
40 | t.Logf("%v", environmentNotDefined)
41 | }
42 |
43 | func TestScenarioStepValid_EnvVariableInPayload(t *testing.T) {
44 | url := "https://test.com"
45 | st := ScenarioStep{
46 | ID: 22,
47 | Name: "",
48 | Method: http.MethodGet,
49 | Auth: Auth{},
50 | Cert: tls.Certificate{},
51 | CertPool: &x509.CertPool{},
52 | Headers: map[string]string{},
53 | Payload: "{{ARGENTINA}}",
54 | URL: url,
55 | Timeout: 0,
56 | Sleep: "",
57 | Custom: map[string]interface{}{},
58 | EnvsToCapture: []EnvCaptureConf{},
59 | }
60 |
61 | definedEnvs := map[string]struct{}{}
62 | err := st.validate(definedEnvs)
63 |
64 | var environmentNotDefined EnvironmentNotDefinedError
65 |
66 | if !errors.As(err, &environmentNotDefined) {
67 | t.Errorf("Should be EnvironmentNotDefinedError")
68 | }
69 |
70 | t.Logf("%v", environmentNotDefined)
71 | }
72 |
73 | func TestScenarioStepValid_EnvVariableInURL(t *testing.T) {
74 | url := "https://test.com/{{ARGENTINA}}"
75 | st := ScenarioStep{
76 | ID: 22,
77 | Name: "",
78 | Method: http.MethodGet,
79 | Auth: Auth{},
80 | Cert: tls.Certificate{},
81 | CertPool: &x509.CertPool{},
82 | Headers: map[string]string{},
83 | Payload: "",
84 | URL: url,
85 | Timeout: 0,
86 | Sleep: "",
87 | Custom: map[string]interface{}{},
88 | EnvsToCapture: []EnvCaptureConf{},
89 | }
90 |
91 | definedEnvs := map[string]struct{}{}
92 | err := st.validate(definedEnvs)
93 |
94 | var environmentNotDefined EnvironmentNotDefinedError
95 |
96 | if !errors.As(err, &environmentNotDefined) {
97 | t.Errorf("Should be EnvironmentNotDefinedError")
98 | }
99 |
100 | t.Logf("%v", environmentNotDefined)
101 | }
102 |
103 | func TestScenarioStep_InvalidCaptureConfig(t *testing.T) {
104 | url := "https://test.com"
105 |
106 | stEmptyFromField := ScenarioStep{
107 | ID: 22,
108 | Name: "",
109 | Method: http.MethodGet,
110 | URL: url,
111 | EnvsToCapture: []EnvCaptureConf{{
112 | Name: "FromHeader",
113 | From: "",
114 | }},
115 | }
116 |
117 | stNoHeaderKey := ScenarioStep{
118 | ID: 22,
119 | Name: "",
120 | Method: http.MethodGet,
121 | URL: url,
122 | EnvsToCapture: []EnvCaptureConf{{
123 | Name: "FromHeader",
124 | From: SourceType(Header),
125 | }},
126 | }
127 |
128 | stNoBodySpecifierKey := ScenarioStep{
129 | ID: 22,
130 | Name: "",
131 | Method: http.MethodGet,
132 | URL: url,
133 | EnvsToCapture: []EnvCaptureConf{{
134 | Name: "FromBody",
135 | From: SourceType(Body),
136 | }},
137 | }
138 |
139 | definedEnvs := map[string]struct{}{}
140 |
141 | tests := []struct {
142 | name string
143 | st ScenarioStep
144 | }{
145 | {"NoHeaderKey", stNoHeaderKey},
146 | {"NoBodySpecifierKey", stNoBodySpecifierKey},
147 | {"EmptyFromField", stEmptyFromField},
148 | }
149 |
150 | for _, test := range tests {
151 | tf := func(t *testing.T) {
152 | // Arrange
153 | err := test.st.validate(definedEnvs)
154 |
155 | var captureConfigError CaptureConfigError
156 |
157 | if !errors.As(err, &captureConfigError) {
158 | t.Errorf("Should be CaptureConfigError")
159 | }
160 | }
161 |
162 | t.Run(test.name, tf)
163 | }
164 | }
165 |
166 | func TestScenarioStepValid_OSEnvVariableInPayload(t *testing.T) {
167 | url := "https://test.com"
168 | st := ScenarioStep{
169 | ID: 22,
170 | Name: "",
171 | Method: http.MethodGet,
172 | Auth: Auth{},
173 | Cert: tls.Certificate{},
174 | CertPool: &x509.CertPool{},
175 | Headers: map[string]string{},
176 | Payload: "{{$SOME_OS_ENV_XX}}",
177 | URL: url,
178 | Timeout: 0,
179 | Sleep: "",
180 | Custom: map[string]interface{}{},
181 | EnvsToCapture: []EnvCaptureConf{},
182 | }
183 |
184 | definedEnvs := map[string]struct{}{}
185 | err := st.validate(definedEnvs)
186 |
187 | var environmentNotDefined EnvironmentNotDefinedError
188 |
189 | if !errors.As(err, &environmentNotDefined) {
190 | t.Errorf("Should be EnvironmentNotDefinedError")
191 | }
192 |
193 | t.Logf("%v", environmentNotDefined)
194 | }
195 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/assertion/lexer/lexer.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import (
4 | "strings"
5 |
6 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/token"
7 | )
8 |
9 | type Lexer struct {
10 | input string
11 | position int // current position in input (points to current char)
12 | readPosition int // current reading position in input (after current char)
13 | ch byte // current char under examination
14 | }
15 |
16 | func New(input string) *Lexer {
17 | l := &Lexer{input: input}
18 | l.readChar()
19 | return l
20 | }
21 |
22 | func (l *Lexer) NextToken() token.Token {
23 | var tok token.Token
24 |
25 | l.skipWhitespace()
26 |
27 | switch l.ch {
28 | case '=':
29 | if l.peekChar() == '=' {
30 | ch := l.ch
31 | l.readChar()
32 | literal := string(ch) + string(l.ch)
33 | tok = token.Token{Type: token.EQ, Literal: literal}
34 | } else {
35 | tok = newToken(token.ILLEGAL, l.ch)
36 | }
37 | case '&':
38 | if l.peekChar() == '&' {
39 | ch := l.ch
40 | l.readChar()
41 | literal := string(ch) + string(l.ch)
42 | tok = token.Token{Type: token.AND, Literal: literal}
43 | } else {
44 | tok = newToken(token.ILLEGAL, l.ch)
45 | }
46 | case '|':
47 | if l.peekChar() == '|' {
48 | ch := l.ch
49 | l.readChar()
50 | literal := string(ch) + string(l.ch)
51 | tok = token.Token{Type: token.OR, Literal: literal}
52 | } else {
53 | tok = newToken(token.ILLEGAL, l.ch)
54 | }
55 | case '+':
56 | tok = newToken(token.PLUS, l.ch)
57 | case '-':
58 | tok = newToken(token.MINUS, l.ch)
59 | case '!':
60 | if l.peekChar() == '=' {
61 | ch := l.ch
62 | l.readChar()
63 | literal := string(ch) + string(l.ch)
64 | tok = token.Token{Type: token.NOT_EQ, Literal: literal}
65 | } else {
66 | tok = newToken(token.BANG, l.ch)
67 | }
68 | case '/':
69 | tok = newToken(token.SLASH, l.ch)
70 | case '*':
71 | tok = newToken(token.ASTERISK, l.ch)
72 | case '<':
73 | tok = newToken(token.LT, l.ch)
74 | case '>':
75 | tok = newToken(token.GT, l.ch)
76 | case ',':
77 | tok = newToken(token.COMMA, l.ch)
78 | case '(':
79 | tok = newToken(token.LPAREN, l.ch)
80 | case ')':
81 | tok = newToken(token.RPAREN, l.ch)
82 | case '{':
83 | tok = newToken(token.LBRACE, l.ch)
84 | case '}':
85 | tok = newToken(token.RBRACE, l.ch)
86 | case '[':
87 | tok = newToken(token.LBRACKET, l.ch)
88 | case ']':
89 | tok = newToken(token.RBRACKET, l.ch)
90 | case ':':
91 | tok = newToken(token.COLON, l.ch)
92 | case 0:
93 | tok.Literal = ""
94 | tok.Type = token.EOF
95 | default:
96 | if l.ch == 34 { // "
97 | l.readChar()
98 | tok.Literal = l.readString()
99 | tok.Type = token.STRING
100 | l.readChar()
101 | return tok
102 | }
103 | if l.ch == 39 { // '
104 | l.readChar()
105 | tok.Literal = l.readRawString()
106 | tok.Type = token.STRING
107 | l.readChar()
108 | return tok
109 | }
110 | if isDigit(l.ch) { // number
111 | tok.Type = token.INT
112 | tok.Literal = l.readNumber()
113 | if strings.Contains(tok.Literal, ".") {
114 | if strings.Count(tok.Literal, ".") > 1 { // more than 1 . is illegal
115 | tok.Type = token.ILLEGAL
116 | }
117 | tok.Type = token.FLOAT
118 | } else {
119 | tok.Type = token.INT
120 | }
121 | return tok
122 | } else if isLetter(l.ch) { // identifier
123 | tok.Literal = l.readIdentifier()
124 | tok.Type = token.LookupIdent(tok.Literal)
125 | return tok
126 | } else {
127 | tok = newToken(token.ILLEGAL, l.ch)
128 | }
129 | }
130 |
131 | l.readChar()
132 | return tok
133 | }
134 |
135 | func (l *Lexer) skipWhitespace() {
136 | for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
137 | l.readChar()
138 | }
139 | }
140 |
141 | func (l *Lexer) readChar() {
142 | if l.readPosition >= len(l.input) {
143 | l.ch = 0 // ASCII code for NUL char
144 | } else {
145 | l.ch = l.input[l.readPosition]
146 | }
147 | l.position = l.readPosition
148 | l.readPosition += 1
149 | }
150 |
151 | func (l *Lexer) peekChar() byte {
152 | if l.readPosition >= len(l.input) {
153 | return 0
154 | } else {
155 | return l.input[l.readPosition]
156 | }
157 | }
158 |
159 | func (l *Lexer) readIdentifier() string {
160 | position := l.position
161 | for isChAllowedInIdent(l.ch) {
162 | l.readChar()
163 | }
164 | return l.input[position:l.position]
165 | }
166 |
167 | func (l *Lexer) readString() string {
168 | position := l.position
169 | for l.ch != 34 { // "
170 | l.readChar()
171 | }
172 | return l.input[position:l.position]
173 | }
174 |
175 | func (l *Lexer) readRawString() string {
176 | position := l.position
177 | for l.ch != 39 { // '
178 | l.readChar()
179 | }
180 | return l.input[position:l.position]
181 | }
182 |
183 | func (l *Lexer) readNumber() string {
184 | position := l.position
185 | for isDigit(l.ch) {
186 | l.readChar()
187 | }
188 | return l.input[position:l.position]
189 | }
190 |
191 | func isChAllowedInIdent(ch byte) bool {
192 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' ||
193 | ch == '.' || ch == '-' || '0' <= ch && ch <= '9'
194 | }
195 |
196 | func isLetter(ch byte) bool { // identifiers
197 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
198 | }
199 |
200 | func isDigit(ch byte) bool {
201 | return '0' <= ch && ch <= '9' || ch == '.'
202 | }
203 |
204 | func newToken(tokenType token.TokenType, ch byte) token.Token {
205 | return token.Token{Type: tokenType, Literal: string(ch)}
206 | }
207 |
--------------------------------------------------------------------------------
/ddosify_engine/core/assertion/service.go:
--------------------------------------------------------------------------------
1 | package assertion
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | "time"
7 |
8 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion"
9 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator"
10 | "go.ddosify.com/ddosify/core/types"
11 | "golang.org/x/exp/slices"
12 | )
13 |
14 | var tickerInterval = 100 // interval in millisecond
15 |
16 | type DefaultAssertionService struct {
17 | assertions map[string]types.TestAssertionOpt // Rule -> Opts
18 | abortChan chan struct{}
19 | doneChan chan struct{}
20 | resChan chan TestAssertionResult
21 | assertEnv *evaluator.AssertEnv
22 | abortTick map[string]int // rule -> tickIndex
23 | iterCount int
24 | mu sync.Mutex
25 | }
26 |
27 | type TestAssertionResult struct {
28 | Fail bool `json:"fail"`
29 | Aborted bool `json:"aborted"`
30 | FailedRules []FailedRule `json:"failed_rules"`
31 | }
32 |
33 | type FailedRule struct {
34 | Rule string `json:"rule"`
35 | ReceivedMap map[string]interface{} `json:"received"`
36 | }
37 |
38 | func NewDefaultAssertionService() (service *DefaultAssertionService) {
39 | return &DefaultAssertionService{}
40 | }
41 |
42 | func (as *DefaultAssertionService) Init(assertions map[string]types.TestAssertionOpt) chan struct{} {
43 | as.assertions = assertions
44 | as.abortChan = make(chan struct{})
45 | as.doneChan = make(chan struct{})
46 | as.resChan = make(chan TestAssertionResult, 1)
47 | totalTime := make([]int64, 0)
48 | as.assertEnv = &evaluator.AssertEnv{TotalTime: totalTime}
49 | as.abortTick = make(map[string]int)
50 | as.mu = sync.Mutex{}
51 | return as.abortChan
52 | }
53 |
54 | func (as *DefaultAssertionService) GetTotalTimes() []int64 {
55 | return as.assertEnv.TotalTime
56 | }
57 | func (as *DefaultAssertionService) GetFailCount() int {
58 | return as.assertEnv.FailCount
59 | }
60 |
61 | func (as *DefaultAssertionService) Start(input <-chan *types.ScenarioResult) {
62 | // get iteration results, add store them cumulatively
63 | firstResult := true
64 | for r := range input {
65 | as.mu.Lock()
66 | as.aggregate(r)
67 | as.mu.Unlock()
68 |
69 | // after first result start checking assertions
70 | if firstResult {
71 | go as.applyAssertions()
72 | firstResult = false
73 | }
74 | }
75 | as.resChan <- as.giveFinalResult()
76 | as.doneChan <- struct{}{}
77 | }
78 |
79 | func (as *DefaultAssertionService) aggregate(r *types.ScenarioResult) {
80 | var iterationTime int64
81 | var iterFailed bool
82 | as.iterCount++
83 | for _, sr := range r.StepResults {
84 | iterationTime += sr.Duration.Milliseconds()
85 | if sr.Err.Type != "" || len(sr.FailedAssertions) > 0 {
86 | iterFailed = true
87 | }
88 | }
89 | if iterFailed {
90 | as.assertEnv.FailCount++
91 | }
92 |
93 | // keep totalTime array sorted
94 | as.insertSorted(iterationTime)
95 |
96 | as.assertEnv.FailCountPerc = float64(as.assertEnv.FailCount) / float64(as.iterCount)
97 | }
98 |
99 | func (as *DefaultAssertionService) applyAssertions() {
100 | ticker := time.NewTicker(time.Duration(tickerInterval) * time.Millisecond)
101 | tickIndex := 1
102 | // apply assertions on the fly for only abort:true ones
103 | assertionsWithAbort := make(map[string]types.TestAssertionOpt)
104 | for rule, opts := range as.assertions {
105 | if opts.Abort {
106 | assertionsWithAbort[rule] = opts
107 | }
108 | }
109 | for range ticker.C {
110 | as.mu.Lock()
111 | var totalTime []int64
112 | totalTime = append(totalTime, as.assertEnv.TotalTime...)
113 | assertEnv := evaluator.AssertEnv{
114 | TotalTime: totalTime,
115 | FailCount: as.assertEnv.FailCount,
116 | }
117 | as.mu.Unlock()
118 |
119 | // apply assertions
120 | for rule, opts := range assertionsWithAbort {
121 | res, _ := assertion.Assert(rule, &assertEnv)
122 | if res == false && opts.Abort {
123 | // if delay is zero, immediately abort
124 | if opts.Delay == 0 || as.abortTick[rule] == tickIndex {
125 | as.abortChan <- struct{}{}
126 | return
127 | }
128 | if _, ok := as.abortTick[rule]; !ok {
129 | // schedule check at
130 | delayTick := (time.Duration(opts.Delay) * time.Second) / (time.Duration(tickerInterval) * time.Millisecond)
131 | as.abortTick[rule] = tickIndex + int(delayTick) - 1
132 | }
133 | }
134 | }
135 | tickIndex++
136 | }
137 | }
138 |
139 | func (as *DefaultAssertionService) giveFinalResult() TestAssertionResult {
140 | // return final result
141 | result := TestAssertionResult{
142 | Fail: false,
143 | }
144 | failedRules := []FailedRule{}
145 | for rule, _ := range as.assertions {
146 | res, err := assertion.Assert(rule, as.assertEnv)
147 | if res == false {
148 | failedRules = append(failedRules, FailedRule{
149 | Rule: rule,
150 | ReceivedMap: err.(assertion.AssertionError).Received(),
151 | })
152 | }
153 | }
154 |
155 | if len(failedRules) > 0 {
156 | result.Fail = true
157 | result.FailedRules = failedRules
158 | }
159 |
160 | return result
161 | }
162 |
163 | func (as *DefaultAssertionService) ResultChan() <-chan TestAssertionResult {
164 | return as.resChan
165 | }
166 |
167 | func (as *DefaultAssertionService) AbortChan() <-chan struct{} {
168 | return as.abortChan
169 | }
170 |
171 | func (as *DefaultAssertionService) DoneChan() <-chan struct{} {
172 | return as.doneChan
173 | }
174 |
175 | func (as *DefaultAssertionService) insertSorted(v int64) {
176 | index := sort.Search(len(as.assertEnv.TotalTime), func(i int) bool { return as.assertEnv.TotalTime[i] >= v })
177 | as.assertEnv.TotalTime = slices.Insert(as.assertEnv.TotalTime, index, v)
178 | }
179 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | info@getanteon.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/ddosify_engine/core/scenario/scripting/assertion/ast/ast.go:
--------------------------------------------------------------------------------
1 | package ast
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | "go.ddosify.com/ddosify/core/scenario/scripting/assertion/token"
8 | )
9 |
10 | // The base Node interface
11 | type Node interface {
12 | TokenLiteral() string
13 | String() string
14 | }
15 |
16 | // All statement nodes implement this
17 | type Statement interface {
18 | Node
19 | statementNode()
20 | }
21 |
22 | // All expression nodes implement this
23 | type Expression interface {
24 | Node
25 | expressionNode()
26 | }
27 |
28 | type ExpressionStatement struct {
29 | Token token.Token // the first token of the expression
30 | Expression Expression
31 | }
32 |
33 | func (es *ExpressionStatement) statementNode() {}
34 | func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }
35 | func (es *ExpressionStatement) String() string {
36 | if es.Expression != nil {
37 | return es.Expression.String()
38 | }
39 | return ""
40 | }
41 |
42 | // Expressions
43 | type Identifier struct {
44 | Token token.Token // the token.IDENT token
45 | Value string
46 | }
47 |
48 | func (i *Identifier) expressionNode() {}
49 | func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
50 | func (i *Identifier) String() string { return i.Value }
51 |
52 | type Boolean struct {
53 | Token token.Token
54 | Value bool
55 | }
56 |
57 | func (b *Boolean) expressionNode() {}
58 | func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
59 | func (b *Boolean) String() string { return b.Token.Literal }
60 | func (il *Boolean) GetVal() interface{} { return il.Value }
61 |
62 | type IntegerLiteral struct {
63 | Token token.Token
64 | Value int64
65 | }
66 |
67 | func (il *IntegerLiteral) expressionNode() {}
68 | func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
69 | func (il *IntegerLiteral) String() string { return il.Token.Literal }
70 | func (il *IntegerLiteral) GetVal() interface{} { return il.Value }
71 |
72 | type FloatLiteral struct {
73 | Token token.Token
74 | Value float64
75 | }
76 |
77 | func (il *FloatLiteral) expressionNode() {}
78 | func (il *FloatLiteral) TokenLiteral() string { return il.Token.Literal }
79 | func (il *FloatLiteral) String() string { return il.Token.Literal }
80 | func (il *FloatLiteral) GetVal() interface{} { return il.Value }
81 |
82 | type NullLiteral struct {
83 | Token token.Token
84 | Value interface{}
85 | }
86 |
87 | func (il *NullLiteral) expressionNode() {}
88 | func (il *NullLiteral) TokenLiteral() string { return il.Token.Literal }
89 | func (il *NullLiteral) String() string { return il.Token.Literal }
90 | func (il *NullLiteral) GetVal() interface{} { return il.Value }
91 |
92 | type StringLiteral struct {
93 | Token token.Token
94 | Value string
95 | }
96 |
97 | func (il *StringLiteral) expressionNode() {}
98 | func (il *StringLiteral) TokenLiteral() string { return il.Token.Literal }
99 | func (il *StringLiteral) String() string { return il.Token.Literal }
100 | func (il *StringLiteral) GetVal() interface{} { return il.Value }
101 |
102 | type ArrayLiteral struct {
103 | Token token.Token
104 | Elems []Expression
105 | }
106 |
107 | func (il *ArrayLiteral) expressionNode() {}
108 | func (il *ArrayLiteral) TokenLiteral() string { return il.Token.Literal }
109 | func (il *ArrayLiteral) String() string {
110 | x := []string{}
111 | for _, e := range il.Elems {
112 | x = append(x, e.String())
113 | }
114 |
115 | return "[" + strings.Join(x, ",") + "]"
116 | }
117 |
118 | type ObjectLiteral struct {
119 | Token token.Token
120 | Elems map[string]Expression
121 | }
122 |
123 | func (il *ObjectLiteral) expressionNode() {}
124 | func (il *ObjectLiteral) TokenLiteral() string { return il.Token.Literal }
125 | func (il *ObjectLiteral) String() string {
126 | x := []string{}
127 | for k, e := range il.Elems {
128 | x = append(x, k+":"+e.String())
129 | }
130 |
131 | return "{" + strings.Join(x, ",") + "}"
132 | }
133 |
134 | type PrefixExpression struct {
135 | Token token.Token // The prefix token, e.g. !
136 | Operator string
137 | Right Expression
138 | }
139 |
140 | func (pe *PrefixExpression) expressionNode() {}
141 | func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }
142 | func (pe *PrefixExpression) String() string {
143 | var out bytes.Buffer
144 |
145 | out.WriteString("(")
146 | out.WriteString(pe.Operator)
147 | out.WriteString(pe.Right.String())
148 | out.WriteString(")")
149 |
150 | return out.String()
151 | }
152 |
153 | type InfixExpression struct {
154 | Token token.Token // The operator token, e.g. +
155 | Left Expression
156 | Operator string
157 | Right Expression
158 | }
159 |
160 | func (oe *InfixExpression) expressionNode() {}
161 | func (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }
162 | func (oe *InfixExpression) String() string {
163 | var out bytes.Buffer
164 |
165 | out.WriteString("(")
166 | out.WriteString(oe.Left.String())
167 | out.WriteString(" " + oe.Operator + " ")
168 | out.WriteString(oe.Right.String())
169 | out.WriteString(")")
170 |
171 | return out.String()
172 | }
173 |
174 | type CallExpression struct {
175 | Token token.Token // The '(' token
176 | Function Expression // Identifier or FunctionLiteral
177 | Arguments []Expression
178 | }
179 |
180 | func (ce *CallExpression) expressionNode() {}
181 | func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }
182 |
183 | func (ce *CallExpression) String() string {
184 | var out bytes.Buffer
185 |
186 | args := []string{}
187 | for _, a := range ce.Arguments {
188 | args = append(args, a.String())
189 | }
190 |
191 | out.WriteString(ce.Function.String())
192 | out.WriteString("(")
193 | out.WriteString(strings.Join(args, ","))
194 | out.WriteString(")")
195 |
196 | return out.String()
197 | }
198 |
--------------------------------------------------------------------------------