From 281e189c0198b0d2b2e53d03af2d0d1acf78f6d1 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Thu, 19 Feb 2026 18:03:18 -0300 Subject: [PATCH 1/5] feat: adding map for enabling/disabling kindling transports --- kindling/client.go | 78 ++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/kindling/client.go b/kindling/client.go index b1fa85c4..673e0c6c 100644 --- a/kindling/client.go +++ b/kindling/client.go @@ -11,6 +11,7 @@ import ( "github.com/getlantern/radiance/common" "github.com/getlantern/radiance/common/reporting" "github.com/getlantern/radiance/common/settings" + "github.com/getlantern/radiance/kindling/dnstt" "github.com/getlantern/radiance/kindling/fronted" "github.com/getlantern/radiance/traces" "go.opentelemetry.io/otel" @@ -23,6 +24,13 @@ var ( kindlingMutex sync.Mutex stopUpdater func() closeTransports []func() error + // EnabledTransports is used for testing purposes for enabling/disabling kindling transports + EnabledTransports = map[string]bool{ + "dnstt": false, + "amp": true, + "proxyless": true, + "fronted": true, + } ) // HTTPClient returns a http client with kindling transport @@ -73,41 +81,59 @@ func NewKindling() kindling.Kindling { ) defer span.End() - updaterCtx, cancel := context.WithCancel(ctx) - f, err := fronted.NewFronted(updaterCtx, reporting.PanicListener, filepath.Join(dataDir, "fronted_cache.json"), logger) - if err != nil { - slog.Error("failed to create fronted client", slog.Any("error", err)) - span.RecordError(err) - } - - ampClient, err := fronted.NewAMPClient(updaterCtx, dataDir, logger) - if err != nil { - slog.Error("failed to create amp client", slog.Any("error", err)) - span.RecordError(err) + closeTransports = []func() error{} + kindlingOptions := []kindling.Option{ + kindling.WithPanicListener(reporting.PanicListener), + kindling.WithLogWriter(logger), } - stopUpdater = cancel - closeTransports = []func() error{ - func() error { + updaterCtx, cancel := context.WithCancel(ctx) + if enabled := EnabledTransports["fronted"]; enabled { + f, err := fronted.NewFronted(updaterCtx, reporting.PanicListener, filepath.Join(dataDir, "fronted_cache.json"), logger) + if err != nil { + slog.Error("failed to create fronted client", slog.Any("error", err)) + span.RecordError(err) + } + closeTransports = append(closeTransports, func() error { if f != nil { f.Close() } return nil - }, - func() error { - return nil - }, + }) + kindlingOptions = append(kindlingOptions, kindling.WithDomainFronting(f)) } - return kindling.NewKindling("radiance", - kindling.WithPanicListener(reporting.PanicListener), - kindling.WithLogWriter(logger), + + if enabled := EnabledTransports["amp"]; enabled { + ampClient, err := fronted.NewAMPClient(updaterCtx, dataDir, logger) + if err != nil { + slog.Error("failed to create amp client", slog.Any("error", err)) + span.RecordError(err) + } + // Kindling will skip amp transports if the request has a payload larger than 6kb + kindlingOptions = append(kindlingOptions, kindling.WithAMPCache(ampClient)) + } + + if enabled := EnabledTransports["dnstt"]; enabled { + dnsttOptions, err := dnstt.DNSTTOptions(updaterCtx, filepath.Join(dataDir, "dnstt.yml.gz"), logger) + if err != nil { + slog.Error("failed to create or load dnstt kindling options", slog.Any("error", err)) + span.RecordError(err) + } + if dnsttOptions != nil { + dnsttOptions.Close() + } + closeTransports = append(closeTransports, dnsttOptions.Close) + kindlingOptions = append(kindlingOptions, kindling.WithDNSTunnel(dnsttOptions)) + } + + if enabled := EnabledTransports["proxyless"]; enabled { // Most endpoints use df.iantem.io, but for some historical reasons // "pro-server" calls still go to api.getiantem.org. - kindling.WithProxyless("df.iantem.io", "api.getiantem.org"), - kindling.WithDomainFronting(f), - // Kindling will skip amp transports if the request has a payload larger than 6kb - kindling.WithAMPCache(ampClient), - ) + kindlingOptions = append(kindlingOptions, kindling.WithProxyless("df.iantem.io", "api.getiantem.org")) + } + + stopUpdater = cancel + return kindling.NewKindling("radiance", kindlingOptions...) } type slogWriter struct { From 1662ff1c6d1fee6ff349b8a819cfbe158ff8b0cd Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Thu, 19 Feb 2026 18:21:58 -0300 Subject: [PATCH 2/5] feat: adding kindling-tester with docker container --- .gitignore | 4 ++ cmd/kindling-tester/README.md | 60 +++++++++++++++++ cmd/kindling-tester/main.go | 106 ++++++++++++++++++++++++++++++ docker/Dockerfile.kindling-tester | 26 ++++++++ 4 files changed, 196 insertions(+) create mode 100644 cmd/kindling-tester/README.md create mode 100644 cmd/kindling-tester/main.go create mode 100644 docker/Dockerfile.kindling-tester diff --git a/.gitignore b/.gitignore index 69c7b126..91978c78 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ build/** .userData config/proxy.conf .vscode/** + +# kindling-tester local env files +cmd/kindling-tester/.env +cmd/kindling-tester/.env.local diff --git a/cmd/kindling-tester/README.md b/cmd/kindling-tester/README.md new file mode 100644 index 00000000..cf9c0e13 --- /dev/null +++ b/cmd/kindling-tester/README.md @@ -0,0 +1,60 @@ +# Kindling transport tester + +Tests individual [kindling](../../kindling) transports (proxyless, fronted, amp, dnstt). +It receives all its arguments via environment variables and uses the kindling HTTP client directly + +## Environment variables + +### Required + +- `DEVICE_ID`: The device ID to use. +- `USER_ID`: The user ID to use. +- `TOKEN`: The auth token to use. +- `RUN_ID`: The run ID. Added to traces as `pinger-id` — useful for looking up a specific run. +- `TARGET_URL`: The URL that will be fetched through kindling. +- `DATA`: Directory for config files, logs, and output artefacts (`output.txt`, `timing.txt`, `success`). + +- `TRANSPORT`: The kindling transport to test. One of: `proxyless`, `fronted`, `amp`, `dnstt`. + +## CLI usage + +```bash +DEVICE_ID=1234 USER_ID=123 TOKEN=mytoken RUN_ID=run1 TARGET_URL=https://example.com DATA=./mydir \ + TRANSPORT=proxyless \ + ./kindling-tester +``` + +Replace `proxyless` with the transport you want to test (`fronted`, `amp`, `dnstt`). + +Upon success the tester writes: +- `DATA/success` — empty marker file +- `DATA/output.txt` — response body +- `DATA/timing.txt` — timing breakdown (connect + fetch latency) + +## Docker usage + +A separate image is built per transport. Each image bakes `TRANSPORT` in at build time via `ENV TRANSPORT=${TRANSPORT}`. + +### Building + +```bash +docker build --build-arg TRANSPORT=proxyless -t radiance-kindling-tester:proxyless -f ./docker/Dockerfile.kindling-tester . +docker build --build-arg TRANSPORT=fronted -t radiance-kindling-tester:fronted -f ./docker/Dockerfile.kindling-tester . +docker build --build-arg TRANSPORT=amp -t radiance-kindling-tester:amp -f ./docker/Dockerfile.kindling-tester . +docker build --build-arg TRANSPORT=dnstt -t radiance-kindling-tester:dnstt -f ./docker/Dockerfile.kindling-tester . +``` + +### Running + +```bash +docker run --rm -v ./mydir:/output \ + -e DEVICE_ID=1234 \ + -e USER_ID=1234 \ + -e TOKEN=mytoken \ + -e RUN_ID=run1 \ + -e TARGET_URL=https://example.com \ + -e DATA=/output \ + radiance-kindling-tester:proxyless +``` + +Swap the image tag to test a different transport; no other flags need to change. diff --git a/cmd/kindling-tester/main.go b/cmd/kindling-tester/main.go new file mode 100644 index 00000000..c9f2a4d2 --- /dev/null +++ b/cmd/kindling-tester/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "fmt" + "io" + "log/slog" + "os" + "strconv" + "time" + + "github.com/getlantern/radiance/common/settings" + "github.com/getlantern/radiance/kindling" +) + +func performKindlingPing(ctx context.Context, urlToHit string, runID string, deviceID string, userID int64, token string, dataDir string) error { + os.MkdirAll(dataDir, 0o755) + settings.Set(settings.DataPathKey, dataDir) + settings.Set(settings.UserIDKey, userID) + settings.Set(settings.TokenKey, token) + settings.Set(settings.UserLevelKey, "") + settings.Set(settings.EmailKey, "pinger@pinger.com") + settings.Set(settings.DevicesKey, []settings.Device{ + { + ID: deviceID, + Name: deviceID, + }, + }) + + t1 := time.Now() + kindling.SetKindling(kindling.NewKindling()) + defer kindling.Close(ctx) + cli := kindling.HTTPClient() + + t2 := time.Now() + // Run the command and capture the output + resp, err := cli.Get(urlToHit) + if err != nil { + slog.Error("failed on get request", slog.Any("error", err)) + return err + } + defer resp.Body.Close() + + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + slog.Error("failed to read response body", slog.Any("error", err)) + return err + } + t3 := time.Now() + slog.Info("lantern ping completed successfully") + // create a marker file that will be used by the pinger to determine success + if err := os.WriteFile(dataDir+"/success", []byte(""), 0o644); err != nil { + slog.Error("failed to write success file", slog.Any("error", err), slog.String("path", dataDir+"/success")) + } + if err := os.WriteFile(dataDir+"/output.txt", responseBody, 0o644); err != nil { + slog.Error("failed to write output file", slog.Any("error", err), slog.String("path", dataDir+"/output.txt")) + } + return os.WriteFile(dataDir+"/timing.txt", []byte(fmt.Sprintf(` + result: %v + run-id: %s + err: %v + started: %s + connected: %d + fetched: %d + url: %s`, + true, runID, nil, t1, int32(t2.Sub(t1).Milliseconds()), int32(t3.Sub(t1).Milliseconds()), urlToHit)), 0o644) +} + +func main() { + deviceID := os.Getenv("DEVICE_ID") + userID := os.Getenv("USER_ID") + token := os.Getenv("TOKEN") + runID := os.Getenv("RUN_ID") + targetURL := os.Getenv("TARGET_URL") + data := os.Getenv("DATA") + transport := os.Getenv("TRANSPORT") + + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }))) + + if deviceID == "" || runID == "" || targetURL == "" || data == "" || transport == "" { + slog.Error("missing required environment variable(s), required environment variables: DEVICE_ID, RUN_ID, TARGET_URL, DATA, TRANSPORT", slog.String("deviceID", deviceID), slog.String("runID", runID), slog.String("targetURL", targetURL), slog.String("data", data), slog.String("transport", transport)) + os.Exit(1) + } + + var uid int64 + + if userID != "" { + var err error + uid, err = strconv.ParseInt(userID, 10, 64) + if err != nil { + slog.Error("failed to parse USER_ID", slog.Any("error", err)) + os.Exit(1) + } + } + + ctx := context.Background() + + kindling.EnabledTransports[transport] = true + slog.Debug("enabled transports", slog.Any("enabled_transports", kindling.EnabledTransports)) + if err := performKindlingPing(ctx, targetURL, runID, deviceID, uid, token, data); err != nil { + slog.Error("failed to perform kindling ping", slog.Any("error", err)) + os.Exit(1) + } +} diff --git a/docker/Dockerfile.kindling-tester b/docker/Dockerfile.kindling-tester new file mode 100644 index 00000000..c00a0313 --- /dev/null +++ b/docker/Dockerfile.kindling-tester @@ -0,0 +1,26 @@ +FROM golang:1.25.1 AS builder + +RUN apt-get update && apt-get install -y git git-lfs bsdmainutils + +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /src + +COPY . . + +ENV GOCACHE=/root/.cache/go-build +RUN --mount=type=cache,target="$GOCACHE" GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /kindling-tester ./cmd/kindling-tester/... + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /kindling-tester /kindling-tester + +# TRANSPORT is set at build time (e.g. --build-arg TRANSPORT=proxyless). +# The entrypoint exports it as the transport-specific env var expected by the binary. +ARG TRANSPORT +ENV TRANSPORT=${TRANSPORT} + +ENTRYPOINT ["sh", "-c", "/kindling-tester"] From 0beec4fe86062508edc03aff65e4d749ab0e7632 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:40:48 -0300 Subject: [PATCH 3/5] fix: disabling all other transports before enabling the selected one --- cmd/kindling-tester/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/kindling-tester/main.go b/cmd/kindling-tester/main.go index c9f2a4d2..c27052ab 100644 --- a/cmd/kindling-tester/main.go +++ b/cmd/kindling-tester/main.go @@ -97,6 +97,11 @@ func main() { ctx := context.Background() + // disabling all other transports before enabling the selected + for transport := range kindling.EnabledTransports { + kindling.EnabledTransports[transport] = false + } + kindling.EnabledTransports[transport] = true slog.Debug("enabled transports", slog.Any("enabled_transports", kindling.EnabledTransports)) if err := performKindlingPing(ctx, targetURL, runID, deviceID, uid, token, data); err != nil { From 5ca4f419048d9fd156e49756140bab5b40c43803 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:56:09 -0300 Subject: [PATCH 4/5] fix: only adding close to the list of closeTransports --- kindling/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kindling/client.go b/kindling/client.go index 140c19d0..071c4861 100644 --- a/kindling/client.go +++ b/kindling/client.go @@ -131,9 +131,8 @@ func NewKindling() kindling.Kindling { span.RecordError(err) } if dnsttOptions != nil { - dnsttOptions.Close() + closeTransports = append(closeTransports, dnsttOptions.Close) } - closeTransports = append(closeTransports, dnsttOptions.Close) kindlingOptions = append(kindlingOptions, kindling.WithDNSTunnel(dnsttOptions)) } From 63c9d013c5b492b0bfb351df4e92a460a60a1d65 Mon Sep 17 00:00:00 2001 From: WendelHime <6754291+WendelHime@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:59:47 -0300 Subject: [PATCH 5/5] fix: renaming var for avoiding rewriting transport --- cmd/kindling-tester/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kindling-tester/main.go b/cmd/kindling-tester/main.go index c27052ab..39675f31 100644 --- a/cmd/kindling-tester/main.go +++ b/cmd/kindling-tester/main.go @@ -98,8 +98,8 @@ func main() { ctx := context.Background() // disabling all other transports before enabling the selected - for transport := range kindling.EnabledTransports { - kindling.EnabledTransports[transport] = false + for name := range kindling.EnabledTransports { + kindling.EnabledTransports[name] = false } kindling.EnabledTransports[transport] = true