├── selfhosted ├── VERSION ├── init_scripts │ ├── postgres │ │ └── 01_postgres_create_dbs.sql │ ├── influxdb │ │ └── 01_influxdb_create_buckets.sh │ └── prometheus │ │ └── prometheus.yml ├── .env ├── nginx │ └── default_reverseproxy.conf ├── README.md └── install.sh ├── ddosify_engine ├── config │ ├── config_testdata │ │ ├── payload.txt │ │ ├── config_empty.json │ │ ├── json_payload_dynamic.json │ │ ├── config_invalid_target.json │ │ ├── xml_payload.xml │ │ ├── data_json_payload.json │ │ ├── config_manual_load.json │ │ ├── test.csv │ │ ├── config_manual_load_override.json │ │ ├── benchmark │ │ │ ├── json_payload.json │ │ │ ├── config_distinct_user.json │ │ │ ├── config_repeated_user.json │ │ │ ├── config_multipart_inject_10rps.json │ │ │ ├── config_multipart_inject_100rps.json │ │ │ ├── config_multipart_inject_1krps.json │ │ │ ├── config_multipart_inject_200rps.json │ │ │ ├── config_multipart_inject_2krps.json │ │ │ ├── config_multipart_inject_500rps.json │ │ │ ├── config_correlation_load_1.json │ │ │ ├── config_correlation_load_2.json │ │ │ ├── config_correlation_load_3.json │ │ │ ├── config_correlation_load_4.json │ │ │ └── config_correlation_load_5.json │ │ ├── json_payload.json │ │ ├── config_payload.json │ │ ├── config_auth.json │ │ ├── config_inject_xml.json │ │ ├── config_multipart_err.json │ │ ├── config_global_envs.json │ │ ├── config_protocol.json │ │ ├── race_configs │ │ │ ├── global_envs.json │ │ │ ├── capture_envs.json │ │ │ ├── step_assertions_stdout.json │ │ │ └── step_assertions_stdout_json.json │ │ ├── config_test_assertion_fail.json │ │ ├── config_invalid_capture_env.json │ │ ├── config.json │ │ ├── config_iteration_count.json │ │ ├── config_debug_false.json │ │ ├── config_debug_mode.json │ │ ├── config_invalid_user_mode_for_cookies.json │ │ ├── config_iteration_count_over_req_count.json │ │ ├── config_init_cookies.json │ │ ├── config_capture_environment.json │ │ ├── config_multipart_payload.json │ │ ├── config_incorrect.json │ │ ├── config_data_csv.json │ │ ├── config_inject_json.json │ │ ├── config_inject_json_dynamic.json │ │ └── test_img.svg │ ├── base.go │ └── base_test.go ├── core │ ├── scenario │ │ ├── scripting │ │ │ ├── assertion │ │ │ │ ├── test_files │ │ │ │ │ ├── a.txt │ │ │ │ │ ├── number.json │ │ │ │ │ ├── jsonArray.json │ │ │ │ │ ├── jsonMap.json │ │ │ │ │ └── currencies.json │ │ │ │ ├── evaluator │ │ │ │ │ ├── env.go │ │ │ │ │ └── function_test.go │ │ │ │ ├── token │ │ │ │ │ └── token.go │ │ │ │ ├── assert.go │ │ │ │ ├── lexer │ │ │ │ │ └── lexer.go │ │ │ │ └── ast │ │ │ │ │ └── ast.go │ │ │ ├── injection │ │ │ │ ├── dynamic_test.go │ │ │ │ └── environment_dynamic.go │ │ │ └── extraction │ │ │ │ ├── regex_test.go │ │ │ │ ├── regex.go │ │ │ │ ├── xml.go │ │ │ │ ├── html.go │ │ │ │ ├── xml_test.go │ │ │ │ ├── base_test.go │ │ │ │ ├── json.go │ │ │ │ ├── html_test.go │ │ │ │ └── base.go │ │ ├── requester │ │ │ ├── base_test.go │ │ │ └── base.go │ │ ├── client_pool.go │ │ └── data │ │ │ └── csv.go │ ├── types │ │ ├── regex │ │ │ ├── regex.go │ │ │ └── regex_test.go │ │ ├── response.go │ │ ├── error.go │ │ └── scenario_test.go │ ├── assertion │ │ ├── base.go │ │ ├── service_test.go │ │ └── service.go │ ├── report │ │ ├── debug_test.go │ │ ├── base_test.go │ │ ├── base.go │ │ └── debug.go │ ├── util │ │ ├── buffer_pool.go │ │ ├── pool.go │ │ └── helper.go │ └── proxy │ │ ├── base_test.go │ │ ├── single.go │ │ └── base.go ├── .dockerignore ├── config_examples │ ├── payload.txt │ ├── assertion │ │ └── expected_body.json │ └── config.json ├── Dockerfile.release ├── Dockerfile ├── .golangci.yml ├── scripts │ ├── testing │ │ └── benchstat.sh │ └── install.sh ├── Dockerfile.dev ├── completions │ ├── README.md │ └── _ddosify ├── go.mod ├── main_exit_test.go ├── Jenkinsfile ├── Jenkinsfile_benchmark └── .goreleaser.yml ├── assets ├── linear.gif ├── waved.gif ├── anteon_stack.png ├── incremental.gif ├── anteon_metrics.png ├── anteon_comparison.jpg ├── anteon_load_test.png ├── anteon_service_map.png ├── ddosify-quick-start.gif ├── anteon_service_summary.png ├── anteon_metrics_detailed.png ├── anteon_load_test_monitoring.png ├── anteon_performance_testing.png ├── anteon_service_map_detail.png ├── anteon_service_map_filtered.png ├── ddosify.profile ├── anteon-logo-db.svg └── anteon-logo-wb.svg ├── .lycheeignore ├── .gitignore ├── .devcontainer ├── .zshrc ├── Dockerfile.dev └── devcontainer.json ├── .github ├── workflows │ ├── docs.yml │ ├── test.yml │ ├── coverage.yml │ └── release.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── SECURITY.md ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /selfhosted/VERSION: -------------------------------------------------------------------------------- 1 | 2.6.4 -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/payload.txt: -------------------------------------------------------------------------------- 1 | Payloaf from file. -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/assertion/test_files/a.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/assertion/test_files/number.json: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /assets/linear.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/linear.gif -------------------------------------------------------------------------------- /assets/waved.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/waved.gif -------------------------------------------------------------------------------- /ddosify_engine/.dockerignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.yml 3 | *.out 4 | Jenkinsfile 5 | README.md 6 | -------------------------------------------------------------------------------- /ddosify_engine/config_examples/payload.txt: -------------------------------------------------------------------------------- 1 | body file 1111111111 2 | body file 22222222222 -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/assertion/test_files/jsonArray.json: -------------------------------------------------------------------------------- 1 | ["xyz","abc"] -------------------------------------------------------------------------------- /assets/anteon_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_stack.png -------------------------------------------------------------------------------- /assets/incremental.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/incremental.gif -------------------------------------------------------------------------------- /assets/anteon_metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_metrics.png -------------------------------------------------------------------------------- /assets/anteon_comparison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_comparison.jpg -------------------------------------------------------------------------------- /assets/anteon_load_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_load_test.png -------------------------------------------------------------------------------- /assets/anteon_service_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_service_map.png -------------------------------------------------------------------------------- /assets/ddosify-quick-start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/ddosify-quick-start.gif -------------------------------------------------------------------------------- /.lycheeignore: -------------------------------------------------------------------------------- 1 | https://getanteon.com/endpoint_1 2 | https://getanteon.com/endpoint_2 3 | http://localhost:8014/ 4 | -------------------------------------------------------------------------------- /assets/anteon_service_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_service_summary.png -------------------------------------------------------------------------------- /assets/anteon_metrics_detailed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_metrics_detailed.png -------------------------------------------------------------------------------- /assets/anteon_load_test_monitoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_load_test_monitoring.png -------------------------------------------------------------------------------- /assets/anteon_performance_testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_performance_testing.png -------------------------------------------------------------------------------- /assets/anteon_service_map_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_service_map_detail.png -------------------------------------------------------------------------------- /assets/anteon_service_map_filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getanteon/anteon/HEAD/assets/anteon_service_map_filtered.png -------------------------------------------------------------------------------- /selfhosted/init_scripts/postgres/01_postgres_create_dbs.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE backend; 2 | CREATE DATABASE alazbackend; 3 | CREATE DATABASE hammermanager; 4 | -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/assertion/test_files/jsonMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "ask": 130.75, 3 | "askSize": 10, 4 | "averageAnalystRating": "2.0 - Buy" 5 | } -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/config_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { 4 | "id": 1, 5 | "url": "test.com" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/json_payload_dynamic.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "{{_randomString}}", 3 | "city" : "{{_randomCity}}", 4 | "age" : "{{_randomInt}}" 5 | } -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/config_invalid_target.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [ 3 | { 4 | "id": 1, 5 | "url": "_invalid.com" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /selfhosted/.env: -------------------------------------------------------------------------------- 1 | DOCKER_INFLUXDB_INIT_USERNAME=admin 2 | DOCKER_INFLUXDB_INIT_PASSWORD=ChangeMe 3 | DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=5yR2qD5zCqqvjwCKKXojnPviQaB87w9JcGweVChXkhWRL 4 | POSTGRES_PASSWORD=ChangeMe -------------------------------------------------------------------------------- /selfhosted/init_scripts/influxdb/01_influxdb_create_buckets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | influx bucket create -n hammerBucketDetailed -o ddosify 5 | influx bucket create -n hammerBucketIteration -o ddosify 6 | -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/xml_payload.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{HELLO}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /ddosify_engine/Dockerfile.release: -------------------------------------------------------------------------------- 1 | FROM alpine:3.15.4 2 | ENV ENV="/root/.ashrc" 3 | WORKDIR /root 4 | RUN apk --no-cache add ca-certificates 5 | COPY ddosify /bin/ 6 | 7 | COPY assets/ddosify.profile /tmp/profile 8 | RUN cat /tmp/profile >> "$ENV" 9 | -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/data_json_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "{{data.info.name}}", 3 | "team" : "{{data.info.team}}", 4 | "city" : "{{data.info.city}}", 5 | "payload" : "{{rand(data.info.payload)}}", 6 | "age" : "{{data.info.age}}" 7 | } -------------------------------------------------------------------------------- /ddosify_engine/core/types/regex/regex.go: -------------------------------------------------------------------------------- 1 | package regex 2 | 3 | const DynamicVariableRegex = `\{{(_)[^}]+\}}` 4 | const JsonDynamicVariableRegex = `\"{{(_)[^}]+\}}"` 5 | 6 | const EnvironmentVariableRegex = `{{[a-zA-Z$][a-zA-Z0-9_().-]*}}` 7 | const JsonEnvironmentVarRegex = `\"{{[a-zA-Z$][a-zA-Z0-9_().-]*}}"` 8 | -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/config_manual_load.json: -------------------------------------------------------------------------------- 1 | { 2 | "manual_load": [ 3 | {"duration": 5, "count": 5}, 4 | {"duration": 6, "count": 10}, 5 | {"duration": 7, "count": 20} 6 | ], 7 | "steps": [ 8 | { 9 | "id": 1, 10 | "url": "test.com" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/test.csv: -------------------------------------------------------------------------------- 1 | Username;City;Team;Payload;Age;Percent;BoolField;;; 2 | Kenan;Tokat;Galatasaray;{"data":{"profile":{"name":"Kenan"}}};25;22.3;true;;; 3 | Fatih;Bolu;Galatasaray;[5,6,7];29;44.3;false;;; 4 | Kursat;Samsun;Besiktas;{"a":"b"};28;12.54;True;;; 5 | Semih;Duzce;Besiktas;{"a":"b"};27;663.67;False;;; 6 | ;;;;;;;;; 7 | ;;;;;;;;; -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/injection/dynamic_test.go: -------------------------------------------------------------------------------- 1 | package injection 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDynamicVariableRace(t *testing.T) { 8 | num := 10 9 | ei := EnvironmentInjector{} 10 | for key := range dynamicFakeDataMap { 11 | for i := 0; i < num; i++ { 12 | go ei.getFakeData(key) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/config_manual_load_override.json: -------------------------------------------------------------------------------- 1 | { 2 | "requests_count": 100, 3 | "duration": 22, 4 | "manual_load": [ 5 | {"duration": 5, "count": 5}, 6 | {"duration": 6, "count": 10}, 7 | {"duration": 7, "count": 20} 8 | ], 9 | "steps": [ 10 | { 11 | "id": 1, 12 | "url": "test.com" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /ddosify_engine/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18.1-alpine as builder 2 | WORKDIR /app 3 | COPY . ./ 4 | RUN go mod download 5 | RUN CGO_ENABLED=0 GOOS=linux go build -o /app/ddosify main.go 6 | 7 | 8 | FROM alpine:3.15.4 9 | ENV ENV="/root/.ashrc" 10 | WORKDIR /root 11 | RUN apk --no-cache add ca-certificates 12 | 13 | COPY --from=builder /app/ddosify /bin/ 14 | 15 | COPY assets/ddosify.profile /tmp/profile 16 | RUN cat /tmp/profile >> "$ENV" 17 | -------------------------------------------------------------------------------- /ddosify_engine/core/assertion/base.go: -------------------------------------------------------------------------------- 1 | package assertion 2 | 3 | import ( 4 | "go.ddosify.com/ddosify/core/types" 5 | ) 6 | 7 | type Aborter interface { 8 | AbortChan() <-chan struct{} 9 | } 10 | 11 | type ResultListener interface { 12 | Start(input <-chan *types.ScenarioResult) 13 | DoneChan() <-chan struct{} // indicates processing of results are done 14 | } 15 | 16 | type Asserter interface { 17 | ResultChan() <-chan TestAssertionResult 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | __debug* 17 | coverage.html 18 | main 19 | 20 | dist/ 21 | ddosify 22 | .vscode 23 | .idea/ 24 | .DS_Store 25 | 26 | -------------------------------------------------------------------------------- /ddosify_engine/config/config_testdata/benchmark/json_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "boolField" : "{{BOOL}}", 3 | "numField" : "{{NUM}}", 4 | "strField" : "{{STR}}", 5 | "numArrayField" : ["{{NUM}}",34], 6 | "strArrayField" : ["{{STR}}","hello"], 7 | "mixedArrayField" : ["{{NUM}}",34,"{{FLOAT}}"], 8 | "{{STR}}" : "xxxx", 9 | "obj" :{ 10 | "numField" : "{{CONTENT_LENGTH}}", 11 | "objectField" : "{{ALL_RESULT}}" 12 | } 13 | } -------------------------------------------------------------------------------- /ddosify_engine/config_examples/assertion/expected_body.json: -------------------------------------------------------------------------------- 1 | [ 2 | "AED", 3 | "ARS", 4 | "AUD", 5 | "BGN", 6 | "BHD", 7 | "BRL", 8 | "CAD", 9 | "CHF", 10 | "CNY", 11 | "DKK", 12 | "DZD", 13 | "EUR", 14 | "FKP", 15 | "INR", 16 | "JEP", 17 | "JPY", 18 | "KES", 19 | "KWD", 20 | "KZT", 21 | "MXN", 22 | "NZD", 23 | "RUB", 24 | "SEK", 25 | "SGD", 26 | "TRY", 27 | "USD" 28 | ] -------------------------------------------------------------------------------- /ddosify_engine/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - lll 4 | - golint 5 | - misspell 6 | linters-settings: 7 | lll: 8 | # max line length, lines longer will be reported. Default is 120. 9 | # '\t' is counted as 1 character by default, and can be changed with the tab-width option 10 | line-length: 120 11 | # tab width in spaces. Default to 1. 12 | tab-width: 1 13 | golint: 14 | min-confidence: 0.85 15 | misspell: 16 | locale: US -------------------------------------------------------------------------------- /ddosify_engine/core/scenario/scripting/assertion/test_files/currencies.json: -------------------------------------------------------------------------------- 1 | [ 2 | "AED", 3 | "ARS", 4 | "AUD", 5 | "BGN", 6 | "BHD", 7 | "BRL", 8 | "CAD", 9 | "CHF", 10 | "CNY", 11 | "DKK", 12 | "DZD", 13 | "EUR", 14 | "FKP", 15 | "INR", 16 | "JEP", 17 | "JPY", 18 | "KES", 19 | "KWD", 20 | "KZT", 21 | "MXN", 22 | "NZD", 23 | "RUB", 24 | "SEK", 25 | "SGD", 26 | "TRY", 27 | "USD" 28 | ] -------------------------------------------------------------------------------- /selfhosted/init_scripts/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 10s 3 | evaluation_interval: 10s 4 | 5 | alerting: 6 | alertmanagers: 7 | - static_configs: 8 | - targets: 9 | 10 | rule_files: 11 | 12 | scrape_configs: 13 | - job_name: "backend" 14 | metrics_path: '/metrics/scrape' 15 | static_configs: 16 | - targets: ["alaz-backend:8008"] 17 | basic_auth: 18 | username: alaz-backend 19 | password: jRTyHAbUHYE37hRBgEz 20 | -------------------------------------------------------------------------------- /assets/ddosify.profile: -------------------------------------------------------------------------------- 1 | export TERM=xterm-256color 2 | NC='\033[0m' 3 | printf "\e[38;5;172m\n" 4 | cat< 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 | Anteon logo dark
3 | Anteon logo light
4 |
5 | 6 |

Anteon Self Hosted: Effortless Kubernetes Monitoring and Performance Testing

7 | 8 |

9 | Anteon Kubernetes Monitoring Service Map 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 | 5 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /assets/anteon-logo-wb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 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 | 5 | 10 | 11 | 12 | 13 | 14 | 20 | 22 | 25 | 28 | 29 | 30 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 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 | --------------------------------------------------------------------------------