Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions dns-dedup/curl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
# Traffic generation for dns-dedup E2E test.
# Exercises single and bulk DNS resolution endpoints.

set -euo pipefail

BASE="http://localhost:8086"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For easier reuse in CI/local runs, consider allowing the base URL to be overridden via an environment variable (similar to other scripts in this repo), instead of hardcoding localhost:8086.

Suggested change
BASE="http://localhost:8086"
BASE="${BASE:-http://localhost:8086}"

Copilot uses AI. Check for mistakes.

echo "=== Single resolve (default domain) ==="
curl -sS "$BASE/resolve"
echo

echo "=== Single resolve (google.com) ==="
curl -sS "$BASE/resolve?domain=google.com"
echo

echo "=== Bulk resolve: 30 lookups for default domain ==="
curl -sS "$BASE/resolve-many?n=30"
echo

echo "=== Bulk resolve: 10 lookups for google.com ==="
curl -sS "$BASE/resolve-many?n=10&domain=google.com"
echo

echo "=== Done ==="
3 changes: 3 additions & 0 deletions dns-dedup/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module dns-dedup

go 1.22.0
103 changes: 103 additions & 0 deletions dns-dedup/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strconv"
"time"
)

// This app tests that Keploy properly deduplicates DNS mocks when the same
// domain returns different IPs on each lookup (round-robin / load-balancing).
//
// AWS services like SQS rotate IPs per DNS query. Before the fix, Keploy
// recorded a new DNS mock for every unique IP set, resulting in thousands of
// duplicate DNS mocks for a single domain.
//
// The /resolve-many endpoint triggers many DNS lookups for the same domain,
// which is the key scenario for verifying deduplication.

func main() {
domain := "sqs.us-east-1.amazonaws.com"
if len(os.Args) > 1 {
domain = os.Args[1]
}

http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})

// Single DNS lookup
http.HandleFunc("/resolve", func(w http.ResponseWriter, r *http.Request) {
d := r.URL.Query().Get("domain")
if d == "" {
d = domain
}
ips, err := net.LookupHost(d)
Comment on lines +36 to +40
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

domain is taken directly from the request and used for DNS lookups. If this sample server is ever run in a non-local environment, it becomes an open DNS “oracle” that can be abused to enumerate internal/private DNS names. Consider restricting allowed domains by default (or gating arbitrary domains behind an explicit flag/env).

Copilot uses AI. Check for mistakes.
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"domain": d,
"ips": ips,
})
})

// Many DNS lookups for the same domain — the key dedup scenario.
// Without dedup, each unique IP set becomes a separate DNS mock.
http.HandleFunc("/resolve-many", func(w http.ResponseWriter, r *http.Request) {
d := r.URL.Query().Get("domain")
if d == "" {
d = domain
}
Comment on lines +55 to +58
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as /resolve: domain is fully user-controlled and used for DNS lookups, which can leak internal DNS information if this service is exposed. Consider an allowlist or a flag to enable arbitrary domains.

Copilot uses AI. Check for mistakes.
n := 20
if ns := r.URL.Query().Get("n"); ns != "" {
if parsed, err := strconv.Atoi(ns); err == nil && parsed > 0 {
n = parsed
}
}
Comment on lines +59 to +64
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The n query parameter is effectively unbounded; a large value can make this handler run for a very long time (and allocate a large results slice), which is risky for CI stability and for anyone accidentally exposing the service. Consider enforcing a reasonable max n (and/or returning 400 when exceeded).

Copilot uses AI. Check for mistakes.

seen := make(map[string]bool)
type result struct {
Iteration int `json:"iteration"`
IPs []string `json:"ips,omitempty"`
New bool `json:"new"`
Error string `json:"error,omitempty"`
}
results := make([]result, 0, n)

for i := 1; i <= n; i++ {
ips, err := net.LookupHost(d)
if err != nil {
results = append(results, result{Iteration: i, Error: err.Error()})
} else {
key := fmt.Sprintf("%v", ips)
isNew := !seen[key]
seen[key] = true
results = append(results, result{Iteration: i, IPs: ips, New: isNew})
Comment on lines +80 to +83
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In /resolve-many, the dedup key is derived from fmt.Sprintf("%v", ips), which is order-sensitive. If DNS responses return the same set of IPs in a different order, this will incorrectly count them as different “unique_ip_sets”. Consider normalizing before keying (e.g., sort the IP slice and join) and using map[string]struct{} for the seen set.

Copilot uses AI. Check for mistakes.
}
time.Sleep(50 * time.Millisecond)
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"domain": d,
"total_queries": n,
"unique_ip_sets": len(seen),
"results": results,
})
})

port := "8086"
fmt.Printf("DNS dedup test server starting on :%s\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
fmt.Fprintf(os.Stderr, "server error: %v\n", err)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The startup error message server error: ... doesn’t provide an actionable next step. Consider including a hint such as checking whether the port is already in use or whether the process has permission to bind the port.

Suggested change
fmt.Fprintf(os.Stderr, "server error: %v\n", err)
fmt.Fprintf(os.Stderr, "server failed to start on :%s: %v\nHint: Check if port %s is already in use or if this process has permission to bind it.\n", port, err, port)

Copilot uses AI. Check for mistakes.
os.Exit(1)
}
}
Loading