Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added flashlight-tester, to be used by pinger #1464

Merged
merged 4 commits into from
Dec 16, 2024
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
43 changes: 43 additions & 0 deletions .github/workflows/buid_tester.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build and Push flashlight tester image
on:
push:
branches: [main]
WendelHime marked this conversation as resolved.
Show resolved Hide resolved
jobs:
docker_push:
name: docker push
runs-on:
group: large-runners
permissions:
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
with:
install: true

- uses: google-github-actions/auth@v2
id: google_auth
with:
token_format: access_token
workload_identity_provider: projects/472305719257/locations/global/workloadIdentityPools/github-actions/providers/ghactions-provider
service_account: [email protected]

- name: docker login
uses: docker/login-action@v3
with:
registry: us-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.google_auth.outputs.access_token }}

- name: docker push
uses: docker/build-push-action@v5
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
file: ./tester/Dockerfile
tags: |
us-docker.pkg.dev/lantern-cloud/containers/flashlight-tester:latest
us-docker.pkg.dev/lantern-cloud/containers/flashlight-tester:${{ github.sha }}
22 changes: 22 additions & 0 deletions tester/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM golang:1.22.6 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 /tester ./tester/...

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y ca-certificates curl && rm -rf /var/lib/apt/lists/*

COPY --from=builder /tester /tester

ENV TRACE=true
ENTRYPOINT ["/tester"]
48 changes: 48 additions & 0 deletions tester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Flashlight tester

This is a simple tester for the Flashlight library.
It receives all it's arguments via environment variables.

> Note: this tester does not time out. It will run indefinitely until it's stopped, or it succeeds in pinging the target URL.

## Environment variables

- `DEVICE_ID`: The device id to use.
- `USER_ID`: The user id to use.
- `TOKEN`: The token to use.
- `RUN_ID`: The run id to use. It will be added to honeycomb traces as value of attribute `pinger-id`. (you can use it for looking up traces for the specific run)
- `TARGET_URL`: The target url to use. This is the url that will be pinged through flashlight.
- `DATA`: The path to use for config files and for logs. This is the path where the output will be written to (proxies.yaml, global.yaml, etc). You can place proxies.yaml there to use it instead of fetching.

All of these are required.

## CLI usage

```bash
DEVICE_ID=1234 USER_ID=123 TOKEN=1234 RUN_ID=1234 TARGET_URL=https://example.com DATA=./mydir ./flashlight-tester
```

The tester will start flashlight, fetch the config & proxies and try to reach the target URL via the socks5 proxy that flashlight provides.
Upoon success, it will write the output of that request to the `output.txt`.

## Docker usage

On each new push to the repository, a new image of the tester is built and pushed to the registry.
It's tagged as `us-docker.pkg.dev/lantern-cloud/containers/flashlight-tester:FLASHLIGHT_HASH`


```bash
docker run --rm -v ./mydir:/output \
-e DEVICE_ID=1234 \
-e USER_ID=1234 \
-e TOKEN=1234 \
-e RUN_ID=1234 \
-e TARGET_URL=https://example.com \
-e DATA=/output \
us-docker.pkg.dev/lantern-cloud/containers/flashlight-tester
```

## Passing custom proxies.yaml

If you want to use a custom proxies.yaml, you can place it in the output directory and it will be used instead of fetching it from the server.
In order for flashlight to pick it up instead of using the fetched config, you need to specify another env variable: `STICKY=true`
176 changes: 176 additions & 0 deletions tester/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"time"

"github.com/google/uuid"

"github.com/getlantern/flashlight/v7"
"github.com/getlantern/flashlight/v7/client"
"github.com/getlantern/flashlight/v7/common"
flashlightOtel "github.com/getlantern/flashlight/v7/otel"
"github.com/getlantern/flashlight/v7/stats"
"github.com/getlantern/golog"
"github.com/getlantern/ops"
)

func configureOtel(country string) {
// Configure OpenTelemetry
const replacementText = "UUID-GOES-HERE"
const honeycombQueryTemplate = `https://ui.honeycomb.io/lantern-bc/environments/prod/datasets/flashlight?query=%7B%22time_range%22%3A300%2C%22granularity%22%3A15%2C%22breakdowns%22%3A%5B%5D%2C%22calculations%22%3A%5B%7B%22op%22%3A%22COUNT%22%7D%5D%2C%22filters%22%3A%5B%7B%22column%22%3A%22pinger-id%22%2C%22op%22%3A%22%3D%22%2C%22value%22%3A%22UUID-GOES-HERE%22%7D%5D%2C%22filter_combination%22%3A%22AND%22%2C%22orders%22%3A%5B%5D%2C%22havings%22%3A%5B%5D%2C%22trace_joins%22%3A%5B%5D%2C%22limit%22%3A100%7D`
runId := uuid.NewString()
fmt.Printf("performing lantern ping: url=%s\n", country)
fmt.Printf("lookup traces on Honeycomb with pinger-id: %s, link: %s\n", runId, strings.ReplaceAll(honeycombQueryTemplate, replacementText, runId))
flashlightOtel.ConfigureOnce(&flashlightOtel.Config{
Endpoint: "api.honeycomb.io:443",
Headers: map[string]string{
"x-honeycomb-team": "vuWkzaeefr2OcL1SfowtuG",
},
}, "pinger")
ops.SetGlobal("pinger-id", runId)
}

func performLanternPing(urlToHit string, runId string, deviceId string, userId int64, token string, dataDir string, isSticky bool) error {
golog.SetPrepender(func(writer io.Writer) {
_, _ = writer.Write([]byte(fmt.Sprintf("%s: ", time.Now().Format("2006-01-02 15:04:05"))))
})

settings := common.NewUserConfigData("lantern", deviceId, userId, token, nil, "en-US")
statsTracker := stats.NewTracker()
var onOneProxy sync.Once
proxyReady := make(chan struct{})
configureOtel(urlToHit)
common.LibraryVersion = "999.999.999"
fc, err := flashlight.New(
"pinger",
"999.999.999",
"10-10-2024",
dataDir,
false,
func() bool { return false },
func() bool { return false },
func() bool { return false },
func() bool { return false },
map[string]interface{}{
"readableconfig": true,
"stickyconfig": isSticky,
},
settings,
statsTracker,
func() bool { return false },
func() string { return "en-US" },
func(host string) (string, error) {
return host, nil
},
func(category, action, label string) {

},
flashlight.WithOnDialError(func(err error, v bool) {
fmt.Printf("failed to dial %v %v\n", err, v)
}),
flashlight.WithOnSucceedingProxy(func() {
onOneProxy.Do(func() {
fmt.Printf("succeeding proxy\n")
proxyReady <- struct{}{}
})
}),
)
if err != nil {
return err
}
resultCh := make(chan error)
t1 := time.Now()
var t2, t3 time.Time
output := ""
go fc.Run("127.0.0.1:0", "127.0.0.1:0", func(cl *client.Client) {
go func() {
sa, ok := cl.Socks5Addr(5 * time.Second)
if !ok {
resultCh <- fmt.Errorf("failed to get socks5 address")
return
}
select {
case <-proxyReady:
break
}

t2 = time.Now()
flashlightProxy := fmt.Sprintf("socks5://%s", sa)
fmt.Printf("lantern started correctly. urlToHit: %s flashlight proxy: %s\n", urlToHit, flashlightProxy)

cmd := exec.Command("curl", "-x", flashlightProxy, "-s", urlToHit)

// Run the command and capture the output
outputB, err := cmd.Output()
if err != nil {
fmt.Println("Error executing command:", err)
resultCh <- err
return
}

output = string(outputB)
t3 = time.Now()
resultCh <- nil
}()
}, func(err error) {
resultCh <- err
})

var runErr error
select {
case err := <-resultCh:
runErr = err
break
}
defer fc.Stop()

if runErr == nil {
fmt.Println("lantern ping completed successfully")
}

_ = os.WriteFile(dataDir+"/output.txt", []byte(output), 0644)

return os.WriteFile(dataDir+"/timing.txt", []byte(fmt.Sprintf(`
result: %v
run-id: %s
err: %v
started: %s
connected: %d
fetched: %d
url: %s`,
runErr == nil, runId, runErr, t1, int32(t2.Sub(t1).Milliseconds()), int32(t3.Sub(t1).Milliseconds()), urlToHit)), 0644)
}

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")
isSticky := os.Getenv("STICKY") == "true"

if deviceId == "" || userId == "" || token == "" || runId == "" || targetUrl == "" || data == "" {
fmt.Println("missing required environment variable(s)")
fmt.Println("Required environment variables: DEVICE_ID, USER_ID, TOKEN, RUN_ID, TARGET_URL, DATA")
os.Exit(1)
}

uid, err := strconv.ParseInt(userId, 10, 64)
if err != nil {
fmt.Println("failed to parse USER_ID")
os.Exit(1)
}

if performLanternPing(targetUrl, runId, deviceId, uid, token, data, isSticky) != nil {
fmt.Println("failed to perform lantern ping")
os.Exit(1)
}
}
Loading