Repository: MorDavid/FlareTunnel
Branch: main
Commit: 8cd8bbec9aea
Files: 11
Total size: 81.7 KB
Directory structure:
gitextract_pkuk33xv/
├── .github/
│ └── FUNDING.yml
├── FlareTunnel.go
├── README.md
├── blacklist-aggressive.txt
├── blacklist-minimal.txt
├── blacklist.txt
├── build.bat
├── build.sh
├── example_usage.py
├── go.mod
└── go.sum
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: mordavid
patreon: mordavid
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: mordavid
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: FlareTunnel.go
================================================
package main
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/olekukonko/tablewriter"
)
// ====================================================================
// CONSTANTS & TYPES
// ====================================================================
const (
Version = "1.0.0"
CloudflareBaseURL = "https://api.cloudflare.com/client/v4"
WorkerScript = `/**
* FlareTunnel - Cloudflare Worker URL Redirection Script
*/
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
try {
const url = new URL(request.url)
const targetUrl = getTargetUrl(url, request.headers)
if (!targetUrl) {
return createErrorResponse('No target URL specified', {
usage: {
query_param: '?url=https://example.com',
header: 'X-Target-URL: https://example.com',
path: '/https://example.com'
}
}, 400)
}
let targetURL
try {
targetURL = new URL(targetUrl)
} catch (e) {
return createErrorResponse('Invalid target URL', { provided: targetUrl }, 400)
}
// Build target URL with filtered query parameters
const targetParams = new URLSearchParams()
for (const [key, value] of url.searchParams) {
if (!['url', '_cb', '_t'].includes(key)) {
targetParams.append(key, value)
}
}
if (targetParams.toString()) {
targetURL.search = targetParams.toString()
}
// Create proxied request
const proxyRequest = createProxyRequest(request, targetURL)
const response = await fetch(proxyRequest)
// Process and return response
return createProxyResponse(response, request.method)
} catch (error) {
return createErrorResponse('Proxy request failed', {
message: error.message,
timestamp: new Date().toISOString()
}, 500)
}
}
function getTargetUrl(url, headers) {
// Priority: query param > header > path
let targetUrl = url.searchParams.get('url')
if (!targetUrl) {
targetUrl = headers.get('X-Target-URL')
}
if (!targetUrl && url.pathname !== '/') {
const pathUrl = url.pathname.slice(1)
if (pathUrl.startsWith('http')) {
targetUrl = pathUrl
}
}
return targetUrl
}
function createProxyRequest(request, targetURL) {
const proxyHeaders = new Headers()
const allowedHeaders = [
'accept', 'accept-language', 'accept-encoding', 'authorization',
'cache-control', 'content-type', 'origin', 'referer', 'user-agent'
]
// Copy allowed headers
for (const [key, value] of request.headers) {
if (allowedHeaders.includes(key.toLowerCase())) {
proxyHeaders.set(key, value)
}
}
proxyHeaders.set('Host', targetURL.hostname)
// Set X-Forwarded-For header
const customXForwardedFor = request.headers.get('X-My-X-Forwarded-For')
if (customXForwardedFor) {
proxyHeaders.set('X-Forwarded-For', customXForwardedFor)
} else {
proxyHeaders.set('X-Forwarded-For', generateRandomIP())
}
return new Request(targetURL.toString(), {
method: request.method,
headers: proxyHeaders,
body: ['GET', 'HEAD'].includes(request.method) ? null : request.body
})
}
function createProxyResponse(response, requestMethod) {
const responseHeaders = new Headers()
// Copy ALL response headers (let browser handle encoding)
for (const [key, value] of response.headers) {
responseHeaders.set(key, value)
}
// Add/Override CORS headers
responseHeaders.set('Access-Control-Allow-Origin', '*')
responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD')
responseHeaders.set('Access-Control-Allow-Headers', '*')
if (requestMethod === 'OPTIONS') {
return new Response(null, { status: 204, headers: responseHeaders })
}
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders
})
}
function createErrorResponse(error, details, status) {
return new Response(JSON.stringify({ error, ...details }), {
status,
headers: { 'Content-Type': 'application/json' }
})
}
function generateRandomIP() {
return [1, 2, 3, 4].map(() => Math.floor(Math.random() * 255) + 1).join('.')
}`
)
// Account represents a Cloudflare account configuration
type Account struct {
Name string `json:"name"`
APIToken string `json:"api_token"`
AccountID string `json:"account_id"`
ZoneID string `json:"zone_id,omitempty"`
}
// Config represents the FlareTunnel configuration
type Config struct {
Accounts []Account `json:"accounts"`
}
// Worker represents a Cloudflare Worker deployment
type Worker struct {
Name string `json:"name"`
URL string `json:"url"`
CreatedAt string `json:"created_at"`
ID string `json:"id"`
AccountID string `json:"account_id"`
ConfigAccountName string `json:"config_account_name,omitempty"`
}
// Analytics represents worker analytics data
type Analytics struct {
Success bool `json:"success"`
TotalRequests int `json:"total_requests"`
PerWorker map[string]int `json:"per_worker"`
Limit int `json:"limit"`
Error string `json:"error,omitempty"`
}
// ====================================================================
// CLOUDFLARE API CLIENT
// ====================================================================
type CloudflareClient struct {
APIToken string
AccountID string
BaseURL string
Headers map[string]string
subdomain string
}
func NewCloudflareClient(apiToken, accountID string) *CloudflareClient {
return &CloudflareClient{
APIToken: apiToken,
AccountID: accountID,
BaseURL: CloudflareBaseURL,
Headers: map[string]string{
"Authorization": "Bearer " + apiToken,
"Content-Type": "application/json",
},
}
}
func (c *CloudflareClient) GetSubdomain() (string, error) {
if c.subdomain != "" {
return c.subdomain, nil
}
url := fmt.Sprintf("%s/accounts/%s/workers/subdomain", c.BaseURL, c.AccountID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
c.subdomain = strings.ToLower(c.AccountID)
return c.subdomain, nil
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
var result struct {
Result struct {
Subdomain string `json:"subdomain"`
} `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
if result.Result.Subdomain != "" {
c.subdomain = result.Result.Subdomain
return c.subdomain, nil
}
}
}
c.subdomain = strings.ToLower(c.AccountID)
return c.subdomain, nil
}
func (c *CloudflareClient) CreateWorker(name string) (*Worker, error) {
if name == "" {
name = generateWorkerName()
}
url := fmt.Sprintf("%s/accounts/%s/workers/scripts/%s", c.BaseURL, c.AccountID, name)
var b bytes.Buffer
writer := multipartWriter(&b)
metadata := map[string]string{
"body_part": "script",
"main_module": "worker.js",
}
metadataJSON, _ := json.Marshal(metadata)
writer.WriteField("metadata", string(metadataJSON))
writer.WriteField("script", WorkerScript)
contentType := writer.FormDataContentType()
writer.Close()
req, err := http.NewRequest("PUT", url, &b)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.APIToken)
req.Header.Set("Content-Type", contentType)
client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to create worker: %s", string(body))
}
// Enable subdomain
subdomain, _ := c.GetSubdomain()
subdomainURL := fmt.Sprintf("%s/accounts/%s/workers/scripts/%s/subdomain", c.BaseURL, c.AccountID, name)
subdomainBody := bytes.NewBufferString(`{"enabled":true}`)
subdomainReq, _ := http.NewRequest("POST", subdomainURL, subdomainBody)
for k, v := range c.Headers {
subdomainReq.Header.Set(k, v)
}
client.Do(subdomainReq)
workerURL := fmt.Sprintf("https://%s.%s.workers.dev", name, subdomain)
return &Worker{
Name: name,
URL: workerURL,
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
ID: name,
AccountID: c.AccountID,
}, nil
}
func (c *CloudflareClient) ListWorkers() ([]*Worker, error) {
url := fmt.Sprintf("%s/accounts/%s/workers/scripts", c.BaseURL, c.AccountID)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Result []struct {
ID string `json:"id"`
CreatedOn string `json:"created_on"`
} `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
subdomain, _ := c.GetSubdomain()
workers := []*Worker{}
for _, script := range result.Result {
if strings.HasPrefix(script.ID, "flaretunnel-") {
workers = append(workers, &Worker{
Name: script.ID,
URL: fmt.Sprintf("https://%s.%s.workers.dev", script.ID, subdomain),
CreatedAt: script.CreatedOn,
AccountID: c.AccountID,
})
}
}
return workers, nil
}
func (c *CloudflareClient) DeleteWorker(name string) error {
url := fmt.Sprintf("%s/accounts/%s/workers/scripts/%s", c.BaseURL, c.AccountID, name)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 404 {
return fmt.Errorf("failed to delete worker: status %d", resp.StatusCode)
}
return nil
}
func (c *CloudflareClient) GetAnalytics() (*Analytics, error) {
graphqlURL := "https://api.cloudflare.com/client/v4/graphql"
now := time.Now()
dateStart := now.Add(-24 * time.Hour).Format("2006-01-02T15:04:05Z")
dateEnd := now.Format("2006-01-02T15:04:05Z")
query := `
query WorkersAnalytics($accountTag: string!, $datetimeStart: string!, $datetimeEnd: string!) {
viewer {
accounts(filter: {accountTag: $accountTag}) {
workersInvocationsAdaptive(
limit: 10000
filter: {
datetime_geq: $datetimeStart
datetime_leq: $datetimeEnd
}
orderBy: [sum_requests_DESC]
) {
dimensions {
scriptName
}
sum {
requests
errors
subrequests
}
}
}
}
}
`
variables := map[string]interface{}{
"accountTag": c.AccountID,
"datetimeStart": dateStart,
"datetimeEnd": dateEnd,
}
payload := map[string]interface{}{
"query": query,
"variables": variables,
}
payloadBytes, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", graphqlURL, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, err
}
for k, v := range c.Headers {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return &Analytics{Success: false, Limit: 100000, PerWorker: make(map[string]int)}, nil
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return &Analytics{Success: false, Limit: 100000, PerWorker: make(map[string]int)}, nil
}
var result struct {
Data struct {
Viewer struct {
Accounts []struct {
WorkersInvocationsAdaptive []struct {
Dimensions struct {
ScriptName string `json:"scriptName"`
} `json:"dimensions"`
Sum struct {
Requests int `json:"requests"`
} `json:"sum"`
} `json:"workersInvocationsAdaptive"`
} `json:"accounts"`
} `json:"viewer"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return &Analytics{Success: false, Limit: 100000, PerWorker: make(map[string]int)}, nil
}
analytics := &Analytics{
Success: true,
Limit: 100000,
PerWorker: make(map[string]int),
}
if len(result.Data.Viewer.Accounts) > 0 {
for _, inv := range result.Data.Viewer.Accounts[0].WorkersInvocationsAdaptive {
scriptName := inv.Dimensions.ScriptName
requests := inv.Sum.Requests
analytics.PerWorker[scriptName] = requests
analytics.TotalRequests += requests
}
}
return analytics, nil
}
// ====================================================================
// SSL CERTIFICATE GENERATION
// ====================================================================
func generateCACert(certPath, keyPath string) error {
if _, err := os.Stat(certPath); err == nil {
if _, err := os.Stat(keyPath); err == nil {
return nil
}
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
notBefore := time.Now()
notAfter := notBefore.Add(3650 * 24 * time.Hour)
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Country: []string{"US"},
Province: []string{"CA"},
Locality: []string{"Local"},
Organization: []string{"FlareTunnel"},
CommonName: "FlareTunnel CA",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return err
}
certOut, err := os.Create(certPath)
if err != nil {
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
keyOut, err := os.Create(keyPath)
if err != nil {
return err
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
keyOut.Close()
fmt.Printf("✓ Generated CA certificate: %s\n", certPath)
return nil
}
func generateHostCert(hostname, caCertPath, caKeyPath string) (*tls.Certificate, error) {
caCertPEM, err := os.ReadFile(caCertPath)
if err != nil {
return nil, err
}
block, _ := pem.Decode(caCertPEM)
caCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
caKeyPEM, err := os.ReadFile(caKeyPath)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(caKeyPEM)
caKey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
return nil, err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumber, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Country: []string{"US"},
Province: []string{"CA"},
Locality: []string{"Local"},
Organization: []string{"FlareTunnel"},
CommonName: hostname,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{hostname, "*." + hostname},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, caCert, &priv.PublicKey, caKey)
if err != nil {
return nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, err
}
return &cert, nil
}
// ====================================================================
// FLARETUNNEL MANAGER
// ====================================================================
type FlareTunnel struct {
Config *Config
Clients map[string]*CloudflareClient
EndpointsFile string
ConfigFile string
workers []*Worker
workersMutex sync.RWMutex
}
func NewFlareTunnel(configFile string) (*FlareTunnel, error) {
if configFile == "" {
configFile = "flaretunnel.json"
}
config, err := loadConfig(configFile)
if err != nil {
return nil, err
}
ft := &FlareTunnel{
Config: config,
Clients: make(map[string]*CloudflareClient),
EndpointsFile: "flaretunnel_endpoints.json",
ConfigFile: configFile,
}
for _, account := range config.Accounts {
ft.Clients[account.Name] = NewCloudflareClient(account.APIToken, account.AccountID)
}
return ft, nil
}
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// Remove BOM if present (UTF-8-sig compatibility)
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
data = data[3:]
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func (ft *FlareTunnel) SaveEndpoints(workers []*Worker) error {
data, err := json.MarshalIndent(workers, "", " ")
if err != nil {
return err
}
return os.WriteFile(ft.EndpointsFile, data, 0644)
}
func (ft *FlareTunnel) LoadEndpoints() ([]*Worker, error) {
data, err := os.ReadFile(ft.EndpointsFile)
if err != nil {
return nil, err
}
// Remove BOM if present
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
data = data[3:]
}
var workers []*Worker
if err := json.Unmarshal(data, &workers); err != nil {
return nil, err
}
return workers, nil
}
func (ft *FlareTunnel) SyncEndpoints() ([]*Worker, error) {
allWorkers := []*Worker{}
for accountName, client := range ft.Clients {
workers, err := client.ListWorkers()
if err != nil {
continue
}
for _, w := range workers {
w.ConfigAccountName = accountName
allWorkers = append(allWorkers, w)
}
}
if len(allWorkers) > 0 {
ft.SaveEndpoints(allWorkers)
}
return allWorkers, nil
}
func (ft *FlareTunnel) CreateWorkers(count int, accountName string, distribute bool) error {
fmt.Printf("\nCreating %d FlareTunnel endpoint(s)...\n", count)
created := []*Worker{}
if accountName != "" {
// Single account
client, ok := ft.Clients[accountName]
if !ok {
return fmt.Errorf("account '%s' not found", accountName)
}
fmt.Printf(" Using account: %s\n", accountName)
for i := 0; i < count; i++ {
worker, err := client.CreateWorker("")
if err != nil {
fmt.Printf(" [%d/%d] Failed: %v\n", i+1, count, err)
continue
}
worker.ConfigAccountName = accountName
created = append(created, worker)
fmt.Printf(" [%d/%d] %s -> %s\n", i+1, count, worker.Name, worker.URL)
}
} else if distribute && len(ft.Clients) > 1 {
// Distribute across accounts
fmt.Printf(" Distribution mode: Checking quotas across %d account(s)...\n", len(ft.Clients))
accountQuotas := make(map[string]int)
for name, client := range ft.Clients {
analytics, _ := client.GetAnalytics()
remaining := 100000 - analytics.TotalRequests
accountQuotas[name] = remaining
fmt.Printf(" %s: %d requests remaining\n", name, remaining)
}
totalQuota := 0
for _, quota := range accountQuotas {
totalQuota += quota
}
if totalQuota == 0 {
totalQuota = len(ft.Clients)
for name := range accountQuotas {
accountQuotas[name] = 1
}
}
workersPerAccount := make(map[string]int)
for name, quota := range accountQuotas {
proportion := float64(quota) / float64(totalQuota)
workersPerAccount[name] = max(1, int(float64(count)*proportion))
}
// Adjust to exact count
for sum := sumMap(workersPerAccount); sum < count; sum = sumMap(workersPerAccount) {
maxAccount := ""
maxQuota := 0
for name, quota := range accountQuotas {
if quota > maxQuota {
maxQuota = quota
maxAccount = name
}
}
workersPerAccount[maxAccount]++
}
fmt.Printf("\n Distribution plan:\n")
for name, wc := range workersPerAccount {
fmt.Printf(" %s: %d worker(s)\n", name, wc)
}
fmt.Println()
createdCount := 0
for name, wc := range workersPerAccount {
client := ft.Clients[name]
for i := 0; i < wc; i++ {
worker, err := client.CreateWorker("")
if err != nil {
fmt.Printf(" [%d/%d] [%s] Failed: %v\n", createdCount+1, count, name, err)
continue
}
worker.ConfigAccountName = name
created = append(created, worker)
createdCount++
fmt.Printf(" [%d/%d] [%s] %s -> %s\n", createdCount, count, name, worker.Name, worker.URL)
}
}
} else {
// Default to first account
var firstClient *CloudflareClient
var firstName string
for name, client := range ft.Clients {
firstClient = client
firstName = name
break
}
fmt.Printf(" Using account: %s\n", firstName)
for i := 0; i < count; i++ {
worker, err := firstClient.CreateWorker("")
if err != nil {
fmt.Printf(" [%d/%d] Failed: %v\n", i+1, count, err)
continue
}
worker.ConfigAccountName = firstName
created = append(created, worker)
fmt.Printf(" [%d/%d] %s -> %s\n", i+1, count, worker.Name, worker.URL)
}
}
ft.SyncEndpoints()
fmt.Printf("\nCreated: %d, Failed: %d\n", len(created), count-len(created))
return nil
}
func (ft *FlareTunnel) ListWorkers(verbose, checkStatus bool) error {
workers, err := ft.SyncEndpoints()
if err != nil || len(workers) == 0 {
fmt.Println("❌ No FlareTunnel endpoints found")
fmt.Println("💡 Create some with: go run FlareTunnel.go create --count 5")
return nil
}
// Verbose mode: check status as well
if verbose {
checkStatus = true // Force status check in verbose mode!
}
// Get analytics for all accounts
allAnalytics := make(map[string]*Analytics)
for accountName, client := range ft.Clients {
analytics, _ := client.GetAnalytics()
allAnalytics[accountName] = analytics
}
if checkStatus {
fmt.Printf("\n🔍 Checking status of %d worker(s)...\n", len(workers))
fmt.Println("This may take a few seconds...\n")
}
fmt.Println()
table := tablewriter.NewWriter(os.Stdout)
// Build header based on flags
header := []string{"#", "Account", "Name"}
if verbose {
header = append(header, "Created", "Age")
}
header = append(header, "URL", "Requests")
if checkStatus {
header = append(header, "Status (ms)")
} else {
header = append(header, "Status")
}
table.SetHeader(header)
table.SetBorder(true)
for idx, worker := range workers {
row := []string{strconv.Itoa(idx), worker.ConfigAccountName, worker.Name}
// Add verbose info (Created, Age)
if verbose {
createdStr := "Unknown"
ageStr := "Unknown"
// Extract timestamp from worker name: flaretunnel-1764721536-kmoiok
re := regexp.MustCompile(`flaretunnel-(\d+)-`)
if matches := re.FindStringSubmatch(worker.Name); len(matches) > 1 {
if timestamp, err := strconv.ParseInt(matches[1], 10, 64); err == nil {
createdTime := time.Unix(timestamp, 0)
createdStr = createdTime.Format("2006-01-02 15:04:05")
// Calculate age
age := time.Since(createdTime)
if age.Hours() < 1 {
ageStr = fmt.Sprintf("%dm ago", int(age.Minutes()))
} else if age.Hours() < 24 {
ageStr = fmt.Sprintf("%dh ago", int(age.Hours()))
} else {
ageStr = fmt.Sprintf("%dd ago", int(age.Hours()/24))
}
}
}
row = append(row, createdStr, ageStr)
}
// Add URL
row = append(row, worker.URL)
// Add requests count
requests := "0"
if analytics, ok := allAnalytics[worker.ConfigAccountName]; ok && analytics.Success {
if req, ok := analytics.PerWorker[worker.Name]; ok {
requests = strconv.Itoa(req)
}
}
row = append(row, requests)
// Add status
if checkStatus {
// Test actual worker
testURL := worker.URL + "?url=" + url.QueryEscape("https://httpbin.org/status/200")
start := time.Now()
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(testURL)
elapsed := time.Since(start)
if err != nil {
row = append(row, "❌ Failed")
} else {
resp.Body.Close()
if resp.StatusCode == 200 {
row = append(row, fmt.Sprintf("✅ %dms", elapsed.Milliseconds()))
} else {
row = append(row, fmt.Sprintf("⚠️ %d", resp.StatusCode))
}
}
} else {
row = append(row, "✅ Active")
}
table.Append(row)
}
table.Render()
fmt.Println("\n💡 Use worker index with: go run FlareTunnel.go tunnel --workers 0,1,2 --verbose")
fmt.Println()
return nil
}
func (ft *FlareTunnel) CleanupWorkers(accountName string) error {
if accountName != "" {
client, ok := ft.Clients[accountName]
if !ok {
return fmt.Errorf("account '%s' not found", accountName)
}
fmt.Printf("\n🗑️ Cleaning up account: %s\n", accountName)
workers, _ := client.ListWorkers()
for _, worker := range workers {
if err := client.DeleteWorker(worker.Name); err != nil {
fmt.Printf(" ✗ Failed to delete: %s\n", worker.Name)
} else {
fmt.Printf(" ✓ Deleted: %s\n", worker.Name)
}
}
} else {
fmt.Printf("\n🗑️ Cleaning up ALL accounts (%d total)\n", len(ft.Clients))
for name, client := range ft.Clients {
fmt.Printf(" Account: %s\n", name)
workers, _ := client.ListWorkers()
for _, worker := range workers {
if err := client.DeleteWorker(worker.Name); err != nil {
fmt.Printf(" ✗ Failed to delete: %s\n", worker.Name)
} else {
fmt.Printf(" ✓ Deleted: %s\n", worker.Name)
}
}
}
os.Remove(ft.EndpointsFile)
}
return nil
}
func (ft *FlareTunnel) TestWorkers(targetURL, method string) error {
workers, err := ft.LoadEndpoints()
if err != nil || len(workers) == 0 {
fmt.Println("❌ No workers available")
return fmt.Errorf("no workers found")
}
fmt.Printf("\nTesting %d FlareTunnel endpoint(s) with %s\n", len(workers), targetURL)
successCount := 0
uniqueIPs := make(map[string]bool)
for _, worker := range workers {
fmt.Printf("\nTesting endpoint: %s\n", worker.Name)
testURL := worker.URL + "?url=" + url.QueryEscape(targetURL)
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest(method, testURL, nil)
if err != nil {
fmt.Printf(" ✗ Request failed: %v\n", err)
continue
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf(" ✗ Request failed: %v\n", err)
continue
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
successCount++
fmt.Printf(" ✓ Request successful! Status: %d\n", resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
bodyStr := strings.TrimSpace(string(body))
// Try to extract IP from common formats
if strings.Contains(targetURL, "ifconfig.me") {
fmt.Printf(" Origin IP: %s\n", bodyStr)
uniqueIPs[bodyStr] = true
} else if strings.Contains(targetURL, "httpbin.org/ip") {
var data map[string]interface{}
if json.Unmarshal(body, &data) == nil {
if origin, ok := data["origin"].(string); ok {
fmt.Printf(" Origin IP: %s\n", origin)
uniqueIPs[origin] = true
}
}
} else {
fmt.Printf(" Response Length: %d bytes\n", len(body))
}
} else {
fmt.Printf(" ✗ Request failed! Status: %d\n", resp.StatusCode)
}
}
fmt.Printf("\nTest Results:\n")
fmt.Printf(" Working endpoints: %d/%d\n", successCount, len(workers))
if len(uniqueIPs) > 0 {
fmt.Printf(" Unique IP addresses: %d\n", len(uniqueIPs))
for ip := range uniqueIPs {
fmt.Printf(" - %s\n", ip)
}
}
return nil
}
func (ft *FlareTunnel) ExportConfig(outputFile string) error {
if _, err := os.Stat("flaretunnel.json"); os.IsNotExist(err) {
fmt.Println("❌ No configuration file found (flaretunnel.json)")
return err
}
configData, err := os.ReadFile("flaretunnel.json")
if err != nil {
fmt.Printf("❌ Failed to read configuration: %v\n", err)
return err
}
exportData := map[string]interface{}{
"exported_at": time.Now().Format(time.RFC3339),
"config": json.RawMessage(configData),
}
data, err := json.MarshalIndent(exportData, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(outputFile, data, 0644); err != nil {
fmt.Printf("❌ Failed to write export file: %v\n", err)
return err
}
var config Config
json.Unmarshal(configData, &config)
fmt.Printf("✅ Exported configuration to %s\n", outputFile)
fmt.Println("\n📊 Summary:")
fmt.Printf(" Accounts: %d\n", len(config.Accounts))
for _, acc := range config.Accounts {
fmt.Printf(" • %s: %s...\n", acc.Name, acc.AccountID[:min(8, len(acc.AccountID))])
}
fmt.Println("\n⚠️ WARNING: This file contains API tokens! Keep it secure! 🔒")
return nil
}
func (ft *FlareTunnel) ImportConfig(inputFile string, merge bool) error {
if _, err := os.Stat(inputFile); os.IsNotExist(err) {
fmt.Printf("❌ Import file not found: %s\n", inputFile)
return err
}
importData, err := os.ReadFile(inputFile)
if err != nil {
fmt.Printf("❌ Failed to read import file: %v\n", err)
return err
}
// Remove BOM if present
if len(importData) >= 3 && importData[0] == 0xEF && importData[1] == 0xBB && importData[2] == 0xBF {
importData = importData[3:]
}
var importWrapper map[string]interface{}
var newConfig Config
if err := json.Unmarshal(importData, &importWrapper); err != nil {
fmt.Printf("❌ Failed to parse import file: %v\n", err)
return err
}
// Check if it's an export format or direct config
if configData, ok := importWrapper["config"]; ok {
configBytes, _ := json.Marshal(configData)
json.Unmarshal(configBytes, &newConfig)
} else {
json.Unmarshal(importData, &newConfig)
}
if len(newConfig.Accounts) == 0 {
fmt.Println("❌ No accounts found in import file")
return fmt.Errorf("no accounts in import")
}
fmt.Printf("📥 Import file: %s\n", inputFile)
fmt.Printf(" Accounts: %d\n\n", len(newConfig.Accounts))
for idx, acc := range newConfig.Accounts {
fmt.Printf(" %d. %s: %s...\n", idx+1, acc.Name, acc.AccountID[:min(8, len(acc.AccountID))])
}
var finalConfig Config
if merge && fileExists("flaretunnel.json") {
existingData, _ := os.ReadFile("flaretunnel.json")
json.Unmarshal(existingData, &finalConfig)
existingNames := make(map[string]bool)
for _, acc := range finalConfig.Accounts {
existingNames[acc.Name] = true
}
for _, acc := range newConfig.Accounts {
if !existingNames[acc.Name] {
finalConfig.Accounts = append(finalConfig.Accounts, acc)
fmt.Printf("➕ Added account: %s\n", acc.Name)
} else {
fmt.Printf("⚠️ Skipped duplicate: %s\n", acc.Name)
}
}
} else {
finalConfig = newConfig
}
reader := bufio.NewReader(os.Stdin)
fmt.Print("\nConfirm import? (y/N): ")
confirm, _ := reader.ReadString('\n')
if strings.ToLower(strings.TrimSpace(confirm)) != "y" {
fmt.Println("Import cancelled.")
return nil
}
data, _ := json.MarshalIndent(finalConfig, "", " ")
if err := os.WriteFile("flaretunnel.json", data, 0644); err != nil {
fmt.Printf("❌ Failed to save configuration: %v\n", err)
return err
}
fmt.Println("\n✅ Configuration imported!")
fmt.Printf("📊 Total accounts: %d\n", len(finalConfig.Accounts))
return nil
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// ====================================================================
// PROXY SERVER
// ====================================================================
type ProxyServer struct {
Host string
Port int
Workers []*Worker
CurrentWorkerIndex int
RotationMode string
Verbose bool
AllowIPAccess bool
CACertPath string
CAKeyPath string
BlacklistPatterns []string
InlineBlockPatterns []string
BlacklistStats map[string]int
UpstreamProxy string
UpstreamVerifySSL bool
CacheCerts bool
NoSSLIntercept bool
mutex sync.Mutex
certCache map[string]*tls.Certificate
certMutex sync.RWMutex
}
func NewProxyServer(host string, port int) *ProxyServer {
return &ProxyServer{
Host: host,
Port: port,
RotationMode: "round-robin",
BlacklistStats: make(map[string]int),
certCache: make(map[string]*tls.Certificate),
}
}
func (ps *ProxyServer) LoadWorkers(endpointsFile string, workerIndices []int) error {
data, err := os.ReadFile(endpointsFile)
if err != nil {
return err
}
var allWorkers []*Worker
if err := json.Unmarshal(data, &allWorkers); err != nil {
return err
}
if len(workerIndices) > 0 {
selected := []*Worker{}
for _, idx := range workerIndices {
if idx >= 0 && idx < len(allWorkers) {
selected = append(selected, allWorkers[idx])
}
}
ps.Workers = selected
} else {
ps.Workers = allWorkers
}
return nil
}
func (ps *ProxyServer) LoadBlacklist(blacklistFile string) error {
patterns := []string{}
// Load from file
if blacklistFile != "" {
if _, err := os.Stat(blacklistFile); err == nil {
file, err := os.Open(blacklistFile)
if err == nil {
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" && !strings.HasPrefix(line, "#") {
patterns = append(patterns, line)
}
}
if len(patterns) > 0 {
fmt.Printf("✅ Loaded %d patterns from %s\n", len(patterns), blacklistFile)
}
}
}
}
// Add inline patterns
if len(ps.InlineBlockPatterns) > 0 {
patterns = append(patterns, ps.InlineBlockPatterns...)
fmt.Printf("✅ Added %d inline patterns\n", len(ps.InlineBlockPatterns))
}
ps.BlacklistPatterns = patterns
return nil
}
func (ps *ProxyServer) GetWorkerURL() string {
if len(ps.Workers) == 0 {
return ""
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
if ps.RotationMode == "random" {
return ps.Workers[time.Now().UnixNano()%int64(len(ps.Workers))].URL
} else if ps.RotationMode == "round-robin" {
worker := ps.Workers[ps.CurrentWorkerIndex]
ps.CurrentWorkerIndex = (ps.CurrentWorkerIndex + 1) % len(ps.Workers)
return worker.URL
}
return ps.Workers[0].URL
}
func (ps *ProxyServer) IsBlacklisted(targetURL string) bool {
if len(ps.BlacklistPatterns) == 0 {
return false
}
urlLower := strings.ToLower(targetURL)
parsedURL, _ := url.Parse(targetURL)
hostname := ""
path := ""
if parsedURL != nil {
hostname = strings.ToLower(parsedURL.Hostname())
path = strings.ToLower(parsedURL.Path)
}
for _, pattern := range ps.BlacklistPatterns {
patternLower := strings.ToLower(pattern)
if strings.Contains(hostname, patternLower) ||
strings.Contains(path, patternLower) ||
strings.Contains(urlLower, patternLower) ||
strings.HasSuffix(urlLower, patternLower) {
ps.mutex.Lock()
ps.BlacklistStats[pattern]++
ps.mutex.Unlock()
return true
}
}
return false
}
func (ps *ProxyServer) HandleHTTP(w http.ResponseWriter, r *http.Request) {
targetURL := r.URL.String()
if !strings.HasPrefix(targetURL, "http://") && !strings.HasPrefix(targetURL, "https://") {
targetURL = "http://" + r.Host + r.URL.String()
}
if ps.IsBlacklisted(targetURL) {
if ps.Verbose {
fmt.Printf("🚫 BLOCKED (blacklist): %s\n", targetURL)
}
http.Error(w, "Blacklisted", http.StatusForbidden)
return
}
parsedURL, _ := url.Parse(targetURL)
if !ps.AllowIPAccess && isIPAddress(parsedURL.Hostname()) {
if ps.Verbose {
fmt.Printf("🛡️ BLOCKED (IP): %s\n", parsedURL.Hostname())
}
http.Error(w, "Direct IP access blocked", http.StatusForbidden)
return
}
workerURL := ps.GetWorkerURL()
if workerURL == "" {
http.Error(w, "No workers available", http.StatusServiceUnavailable)
return
}
proxyURL := workerURL + "?url=" + url.QueryEscape(targetURL)
if ps.Verbose {
fmt.Printf("\n📤 [%s] %s\n", r.Method, targetURL)
fmt.Printf(" ↓ via Worker: %s\n", workerURL)
}
// Create proxy request
proxyReq, err := http.NewRequest(r.Method, proxyURL, r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Copy headers
for k, v := range r.Header {
if !strings.EqualFold(k, "Host") && !strings.EqualFold(k, "Connection") {
proxyReq.Header[k] = v
}
}
// Send request with optional upstream proxy
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !ps.UpstreamVerifySSL},
}
if ps.UpstreamProxy != "" {
proxyURL, err := url.Parse(ps.UpstreamProxy)
if err == nil {
transport.Proxy = http.ProxyURL(proxyURL)
}
}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(proxyReq)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
// Copy response headers
for k, v := range resp.Header {
w.Header()[k] = v
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
if ps.Verbose {
status := "✅"
if resp.StatusCode >= 400 {
status = "⚠️"
}
fmt.Printf(" ↑ %s %d\n", status, resp.StatusCode)
}
}
func (ps *ProxyServer) HandleCONNECT(w http.ResponseWriter, r *http.Request) {
if ps.CACertPath == "" {
http.Error(w, "HTTPS not supported", http.StatusNotImplemented)
return
}
host := r.Host
hostname := strings.Split(host, ":")[0]
if ps.Verbose {
fmt.Printf("\n🔒 [CONNECT] %s\n", host)
}
// Get or generate certificate
ps.certMutex.RLock()
cert, exists := ps.certCache[hostname]
ps.certMutex.RUnlock()
if !exists {
newCert, err := generateHostCert(hostname, ps.CACertPath, ps.CAKeyPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
cert = newCert
ps.certMutex.Lock()
ps.certCache[hostname] = cert
ps.certMutex.Unlock()
}
// Send 200 Connection Established
w.WriteHeader(http.StatusOK)
// Hijack connection
hijacker, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
defer clientConn.Close()
// Wrap with TLS
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*cert},
}
tlsConn := tls.Server(clientConn, tlsConfig)
defer tlsConn.Close()
if err := tlsConn.Handshake(); err != nil {
if ps.Verbose {
fmt.Printf("✗ SSL handshake failed: %v\n", err)
}
return
}
// Read HTTPS request
reader := bufio.NewReader(tlsConn)
req, err := http.ReadRequest(reader)
if err != nil {
return
}
targetURL := "https://" + hostname + req.URL.String()
if ps.IsBlacklisted(targetURL) {
if ps.Verbose {
fmt.Printf(" 🚫 BLOCKED (blacklist): %s\n", targetURL)
}
return
}
workerURL := ps.GetWorkerURL()
if workerURL == "" {
return
}
proxyURL := workerURL + "?url=" + url.QueryEscape(targetURL)
if ps.Verbose {
fmt.Printf(" 📤 [%s] %s\n", req.Method, targetURL)
fmt.Printf(" ↓ via Worker: %s\n", workerURL)
}
// Create proxy request
proxyReq, err := http.NewRequest(req.Method, proxyURL, req.Body)
if err != nil {
return
}
for k, v := range req.Header {
if !strings.EqualFold(k, "Host") && !strings.EqualFold(k, "Connection") {
proxyReq.Header[k] = v
}
}
// Setup transport with optional upstream proxy
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !ps.UpstreamVerifySSL},
}
if ps.UpstreamProxy != "" {
upstreamURL, err := url.Parse(ps.UpstreamProxy)
if err == nil {
transport.Proxy = http.ProxyURL(upstreamURL)
}
}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(proxyReq)
if err != nil {
return
}
defer resp.Body.Close()
// Write response
tlsConn.Write([]byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", resp.StatusCode, resp.Status)))
for k, v := range resp.Header {
for _, vv := range v {
tlsConn.Write([]byte(fmt.Sprintf("%s: %s\r\n", k, vv)))
}
}
tlsConn.Write([]byte("\r\n"))
io.Copy(tlsConn, resp.Body)
if ps.Verbose {
status := "✅"
if resp.StatusCode >= 400 {
status = "⚠️"
}
fmt.Printf(" ↑ %s %d\n", status, resp.StatusCode)
}
}
func (ps *ProxyServer) Start(blacklistFile string) error {
// Setup SSL
ps.CACertPath = "flaretunnel_ca.crt"
ps.CAKeyPath = "flaretunnel_ca.key"
if !ps.NoSSLIntercept {
if err := generateCACert(ps.CACertPath, ps.CAKeyPath); err != nil {
fmt.Printf("⚠️ SSL setup failed: %v\n", err)
ps.CACertPath = ""
}
} else {
ps.CACertPath = ""
}
// Load blacklist
ps.LoadBlacklist(blacklistFile)
fmt.Println("\n" + strings.Repeat("=", 80))
fmt.Println("🚀 FlareTunnel Tunnel Server Started")
fmt.Println(strings.Repeat("=", 80))
fmt.Printf("📡 Listening: %s:%d\n", ps.Host, ps.Port)
fmt.Printf("⚙️ Workers: %d\n", len(ps.Workers))
fmt.Printf("🔄 Rotation: %s\n", ps.RotationMode)
if ps.NoSSLIntercept {
fmt.Printf("🔒 SSL/HTTPS: ✗ Disabled by --no-ssl-intercept\n")
} else if ps.CACertPath != "" {
fmt.Printf("🔒 SSL/HTTPS: ✓ Enabled (HTTPS CONNECT supported)\n")
} else {
fmt.Printf("🔒 SSL/HTTPS: ✗ Disabled\n")
}
if ps.AllowIPAccess {
fmt.Printf("🛡️ IP Blocking: ❌ Disabled (--unsafe)\n")
} else {
fmt.Printf("🛡️ IP Blocking: ✅ Enabled (saves Worker requests)\n")
}
blacklistMsg := fmt.Sprintf("🚫 Blacklist: %d pattern(s)", len(ps.BlacklistPatterns))
if blacklistFile == "blacklist-minimal.txt" && len(ps.BlacklistPatterns) > 0 {
blacklistMsg += " ✅ (default: blacklist-minimal.txt)"
}
fmt.Println(blacklistMsg)
if ps.UpstreamProxy != "" {
fmt.Printf("🔗 Upstream: %s\n", ps.UpstreamProxy)
} else {
fmt.Printf("🔗 Upstream: ❌ None (direct to Workers)\n")
}
if ps.Verbose {
fmt.Printf("📝 Verbose: ✅ Enabled\n")
} else {
fmt.Printf("📝 Verbose: ❌ Disabled\n")
}
fmt.Println("\n📋 Selected Workers:")
for idx, worker := range ps.Workers {
workerName := worker.Name
if len(workerName) > 50 {
workerName = workerName[:47] + "..."
}
fmt.Printf(" [%d] %s\n", idx, workerName)
}
if ps.UpstreamProxy != "" {
fmt.Println("\n🔀 Request Flow:")
fmt.Printf(" Client → FlareTunnel:%d → %s → Cloudflare Workers → Target\n", ps.Port, ps.UpstreamProxy)
}
fmt.Println("\n⚙️ Proxy Configuration:")
fmt.Printf(" HTTP Proxy: %s:%d\n", ps.Host, ps.Port)
fmt.Printf(" HTTPS Proxy: %s:%d\n", ps.Host, ps.Port)
if ps.CACertPath != "" {
fmt.Println("\n🔐 For HTTPS without warnings, install CA certificate:")
fmt.Printf(" 📄 File: %s\n", ps.CACertPath)
fmt.Printf(" 💡 Or use verify=False in code\n")
}
fmt.Println("\n💡 Tips:")
if len(ps.BlacklistPatterns) > 0 {
fmt.Println(" • Blacklist active - saving Worker requests! 💰")
} else {
fmt.Println(" • No blacklist - use --blacklist blacklist-minimal.txt to save requests")
}
if !ps.AllowIPAccess {
fmt.Println(" • IP blocking active - Cloudflare doesn't support IPs anyway")
}
fmt.Println(" • Press Ctrl+C to stop")
fmt.Println(strings.Repeat("=", 80))
fmt.Println()
// Start server
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
ps.HandleCONNECT(w, r)
} else {
ps.HandleHTTP(w, r)
}
})
server := &http.Server{
Addr: fmt.Sprintf("%s:%d", ps.Host, ps.Port),
Handler: handler,
}
return server.ListenAndServe()
}
// ====================================================================
// UTILITY FUNCTIONS
// ====================================================================
func generateWorkerName() string {
timestamp := time.Now().Unix()
suffix := randomString(6)
return fmt.Sprintf("flaretunnel-%d-%s", timestamp, suffix)
}
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyz"
b := make([]byte, n)
for i := range b {
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
time.Sleep(1 * time.Nanosecond)
}
return string(b)
}
func isIPAddress(hostname string) bool {
host := strings.Split(hostname, ":")[0]
ip := net.ParseIP(host)
return ip != nil
}
func testWorker(workerURL string) bool {
testURL := workerURL + "?url=" + url.QueryEscape("https://httpbin.org/status/200")
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(testURL)
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == 200
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func sumMap(m map[string]int) int {
sum := 0
for _, v := range m {
sum += v
}
return sum
}
// Simple multipart writer
type simpleMultipartWriter struct {
buf *bytes.Buffer
boundary string
}
func multipartWriter(buf *bytes.Buffer) *simpleMultipartWriter {
return &simpleMultipartWriter{
buf: buf,
boundary: "----FlareTunnelBoundary" + randomString(16),
}
}
func (w *simpleMultipartWriter) WriteField(field, value string) {
w.buf.WriteString("--" + w.boundary + "\r\n")
w.buf.WriteString(fmt.Sprintf("Content-Disposition: form-data; name=\"%s\"\r\n\r\n", field))
w.buf.WriteString(value + "\r\n")
}
func (w *simpleMultipartWriter) Close() {
w.buf.WriteString("--" + w.boundary + "--\r\n")
}
func (w *simpleMultipartWriter) FormDataContentType() string {
return "multipart/form-data; boundary=" + w.boundary
}
// ====================================================================
// CLI COMMANDS
// ====================================================================
func setupConfig() error {
fmt.Println(strings.Repeat("=", 70))
fmt.Println("🔧 FlareTunnel Multi-Account Configuration")
fmt.Println(strings.Repeat("=", 70))
fmt.Println()
fmt.Println("Getting Cloudflare Credentials:")
fmt.Println("1. Sign up at https://cloudflare.com")
fmt.Println("2. Go to https://dash.cloudflare.com/profile/api-tokens")
fmt.Println("3. Click Create Token - use 'Edit Cloudflare Workers' template")
fmt.Println("4. Set account and zone resources to all")
fmt.Println("5. Copy the token and Account ID")
fmt.Println()
fmt.Println("💡 Tip: You can add multiple accounts for more quota!")
fmt.Println(" Each account = 100,000 requests/day")
fmt.Println()
accounts := []Account{}
reader := bufio.NewReader(os.Stdin)
for accountNum := 1; ; accountNum++ {
fmt.Printf("\n📋 Account #%d\n", accountNum)
fmt.Println(strings.Repeat("-", 40))
fmt.Printf("Account name (e.g., 'main', 'backup') [account-%d]: ", accountNum)
accountName, _ := reader.ReadString('\n')
accountName = strings.TrimSpace(accountName)
if accountName == "" {
accountName = fmt.Sprintf("account-%d", accountNum)
}
fmt.Print("API token: ")
apiToken, _ := reader.ReadString('\n')
apiToken = strings.TrimSpace(apiToken)
if apiToken == "" {
if accountNum == 1 {
fmt.Println("❌ API token is required")
return fmt.Errorf("no API token provided")
}
break
}
fmt.Print("Account ID: ")
accountID, _ := reader.ReadString('\n')
accountID = strings.TrimSpace(accountID)
if accountID == "" {
if accountNum == 1 {
fmt.Println("❌ Account ID is required")
return fmt.Errorf("no account ID provided")
}
break
}
accounts = append(accounts, Account{
Name: accountName,
APIToken: apiToken,
AccountID: accountID,
})
fmt.Printf("✅ Account '%s' added!\n", accountName)
if accountNum >= 1 {
fmt.Print("\nAdd another account? (y/N): ")
addMore, _ := reader.ReadString('\n')
if strings.ToLower(strings.TrimSpace(addMore)) != "y" {
break
}
}
}
if len(accounts) == 0 {
fmt.Println("❌ No accounts configured")
return fmt.Errorf("no accounts configured")
}
config := Config{Accounts: accounts}
data, _ := json.MarshalIndent(config, "", " ")
if err := os.WriteFile("flaretunnel.json", data, 0644); err != nil {
fmt.Printf("❌ Error saving configuration: %v\n", err)
return err
}
fmt.Println()
fmt.Println(strings.Repeat("=", 70))
fmt.Println("✅ Configuration saved!")
fmt.Println(strings.Repeat("=", 70))
fmt.Println("📄 Config file: flaretunnel.json")
fmt.Printf("📊 Accounts configured: %d\n", len(accounts))
fmt.Printf("🚀 Total daily quota: %d requests\n", len(accounts)*100000)
fmt.Println()
for _, acc := range accounts {
fmt.Printf(" • %s: %s...\n", acc.Name, acc.AccountID[:8])
}
fmt.Println()
fmt.Println("🎉 FlareTunnel is now configured!")
return nil
}
func parseWorkerIndices(indicesStr string) []int {
indices := []int{}
parts := strings.Split(indicesStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if strings.Contains(part, "-") {
rangeParts := strings.Split(part, "-")
if len(rangeParts) == 2 {
start, err1 := strconv.Atoi(strings.TrimSpace(rangeParts[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(rangeParts[1]))
if err1 == nil && err2 == nil {
for i := start; i <= end; i++ {
indices = append(indices, i)
}
}
}
} else {
if idx, err := strconv.Atoi(part); err == nil {
indices = append(indices, idx)
}
}
}
return indices
}
// ====================================================================
// MAIN
// ====================================================================
func main() {
if len(os.Args) < 2 {
printHelp()
return
}
command := os.Args[1]
switch command {
case "config":
setupConfig()
case "create":
count := 1
accountName := ""
distribute := false
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--count":
if i+1 < len(os.Args) {
count, _ = strconv.Atoi(os.Args[i+1])
i++
}
case "--account":
if i+1 < len(os.Args) {
accountName = os.Args[i+1]
i++
}
case "--distribute":
distribute = true
}
}
ft, err := NewFlareTunnel("flaretunnel.json")
if err != nil {
fmt.Printf("❌ Configuration error: %v\n", err)
fmt.Println("Run: go run FlareTunnel.go config")
return
}
ft.CreateWorkers(count, accountName, distribute)
case "list":
verbose := false
checkStatus := false
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--verbose", "-v":
verbose = true
case "--status":
checkStatus = true
}
}
// If both flags provided, verbose already includes status check
if verbose && checkStatus {
fmt.Println("⚠️ Note: --verbose already includes live status check, --status flag is redundant\n")
// checkStatus is already covered by verbose, no need to set it
}
ft, err := NewFlareTunnel("flaretunnel.json")
if err != nil {
fmt.Printf("❌ Configuration error: %v\n", err)
return
}
ft.ListWorkers(verbose, checkStatus)
case "test":
targetURL := "https://ifconfig.me/ip"
method := "GET"
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--url":
if i+1 < len(os.Args) {
targetURL = os.Args[i+1]
i++
}
case "--method":
if i+1 < len(os.Args) {
method = os.Args[i+1]
i++
}
}
}
ft, err := NewFlareTunnel("flaretunnel.json")
if err != nil {
fmt.Printf("❌ Configuration error: %v\n", err)
return
}
ft.TestWorkers(targetURL, method)
case "export":
outputFile := "flaretunnel_config_backup.json"
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--output":
if i+1 < len(os.Args) {
outputFile = os.Args[i+1]
i++
}
}
}
ft, _ := NewFlareTunnel("flaretunnel.json")
ft.ExportConfig(outputFile)
case "import":
inputFile := ""
merge := false
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--input":
if i+1 < len(os.Args) {
inputFile = os.Args[i+1]
i++
}
case "--merge":
merge = true
}
}
if inputFile == "" {
fmt.Println("❌ Error: --input required for import command")
fmt.Println("Usage: go run FlareTunnel.go import --input config_backup.json [--merge]")
return
}
ft := &FlareTunnel{}
ft.ImportConfig(inputFile, merge)
case "cleanup":
accountName := ""
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--account":
if i+1 < len(os.Args) {
accountName = os.Args[i+1]
i++
}
}
}
ft, err := NewFlareTunnel("flaretunnel.json")
if err != nil {
fmt.Printf("❌ Configuration error: %v\n", err)
return
}
reader := bufio.NewReader(os.Stdin)
if accountName != "" {
fmt.Printf("Delete ALL workers from account '%s'? (y/N): ", accountName)
} else {
fmt.Print("Delete ALL FlareTunnel endpoints from ALL accounts? (y/N): ")
}
confirm, _ := reader.ReadString('\n')
if strings.ToLower(strings.TrimSpace(confirm)) == "y" {
ft.CleanupWorkers(accountName)
} else {
fmt.Println("Cleanup cancelled.")
}
case "tunnel":
host := "127.0.0.1"
port := 8080
workersStr := ""
mode := "round-robin"
verbose := false
unsafe := false
blacklist := "blacklist-minimal.txt"
upstreamProxy := ""
upstreamVerifySSL := false
cacheCerts := false
noSSLIntercept := false
blockPatterns := []string{}
for i := 2; i < len(os.Args); i++ {
switch os.Args[i] {
case "--host":
if i+1 < len(os.Args) {
host = os.Args[i+1]
i++
}
case "--port":
if i+1 < len(os.Args) {
port, _ = strconv.Atoi(os.Args[i+1])
i++
}
case "--workers":
if i+1 < len(os.Args) {
workersStr = os.Args[i+1]
i++
}
case "--mode":
if i+1 < len(os.Args) {
mode = os.Args[i+1]
i++
}
case "--verbose", "-v":
verbose = true
case "--unsafe":
unsafe = true
case "--blacklist":
if i+1 < len(os.Args) {
blacklist = os.Args[i+1]
i++
}
case "--upstream-proxy":
if i+1 < len(os.Args) {
upstreamProxy = os.Args[i+1]
i++
}
case "--upstream-verify-ssl":
upstreamVerifySSL = true
case "--cache-certs":
cacheCerts = true
case "--no-ssl-intercept":
noSSLIntercept = true
case "--block":
if i+1 < len(os.Args) {
blockPatterns = append(blockPatterns, os.Args[i+1])
i++
}
}
}
ps := NewProxyServer(host, port)
ps.RotationMode = mode
ps.Verbose = verbose
ps.AllowIPAccess = unsafe
ps.UpstreamProxy = upstreamProxy
ps.UpstreamVerifySSL = upstreamVerifySSL
ps.CacheCerts = cacheCerts
ps.NoSSLIntercept = noSSLIntercept
// Add inline block patterns
if len(blockPatterns) > 0 {
ps.InlineBlockPatterns = blockPatterns
}
var workerIndices []int
if workersStr != "" {
workerIndices = parseWorkerIndices(workersStr)
}
if err := ps.LoadWorkers("flaretunnel_endpoints.json", workerIndices); err != nil {
fmt.Printf("❌ Failed to load workers: %v\n", err)
fmt.Println("Run: go run FlareTunnel.go create --count 5")
return
}
if len(ps.Workers) == 0 {
fmt.Println("❌ No workers available")
return
}
ps.Start(blacklist)
default:
printHelp()
}
}
func printHelp() {
help := `
FlareTunnel - Cloudflare Workers Proxy System (Go Version)
Usage: go run FlareTunnel.go <command> [options]
Commands:
config Configure Cloudflare API credentials (interactive)
create Create new Cloudflare Workers
list List all Workers with analytics
test Test Workers with a target URL
export Export configuration to backup file
import Import configuration from backup file
cleanup Delete Workers (prompts for confirmation)
tunnel Start local proxy server
Examples:
# Configuration
go run FlareTunnel.go config
# Create Workers
go run FlareTunnel.go create --count 5
go run FlareTunnel.go create --count 10 --distribute
go run FlareTunnel.go create --count 3 --account main
# List Workers
go run FlareTunnel.go list # Basic list
go run FlareTunnel.go list --verbose # Detailed + live status check
go run FlareTunnel.go list --status # Only live response times
# Test Workers
go run FlareTunnel.go test
go run FlareTunnel.go test --url https://httpbin.org/ip
go run FlareTunnel.go test --url https://example.com --method POST
# Export/Import Config
go run FlareTunnel.go export --output my_backup.json
go run FlareTunnel.go import --input my_backup.json
go run FlareTunnel.go import --input config.json --merge
# Start Tunnel
go run FlareTunnel.go tunnel --verbose
go run FlareTunnel.go tunnel --workers 0,1,2 --mode random
go run FlareTunnel.go tunnel --port 9090 --blacklist blacklist.txt
go run FlareTunnel.go tunnel --upstream-proxy http://127.0.0.1:8888
# Cleanup
go run FlareTunnel.go cleanup
go run FlareTunnel.go cleanup --account main
Options:
Create:
--count N Number of workers to create (default: 1)
--account NAME Create on specific account
--distribute Auto-distribute across accounts based on quota
List:
--verbose, -v Show detailed info + live status check (created, age, response times)
--status Check only live worker status (response times, no verbose info)
Test:
--url URL Target URL to test (default: https://ifconfig.me/ip)
--method METHOD HTTP method (default: GET)
Export:
--output FILE Output file (default: flaretunnel_config_backup.json)
Import:
--input FILE Input config backup file (REQUIRED)
--merge Merge with existing config (skip duplicates)
Cleanup:
--account NAME Delete from specific account only
Tunnel:
--host HOST Bind host (default: 127.0.0.1)
--port PORT Bind port (default: 8080)
--workers INDICES Worker indices (e.g., '0,1,2' or '0-2')
--mode MODE Rotation mode: random, round-robin (default: round-robin)
--verbose, -v Enable verbose logging
--unsafe Allow IP access (not recommended)
--upstream-proxy URL Upstream proxy (e.g., http://127.0.0.1:8080)
--upstream-verify-ssl Verify SSL for upstream proxy
--cache-certs Cache SSL certs to disk
--no-ssl-intercept Disable SSL interception
--blacklist FILE Blacklist file (default: blacklist-minimal.txt)
--block PATTERN Block pattern (can be used multiple times)
`
fmt.Println(help)
}
================================================
FILE: README.md
================================================
# 🚀 FlareTunnel
<div align="center">
<img src="logo.png" alt="FlareTunnel" width="300">
**A unified proxy system that routes traffic through Cloudflare Workers for IP rotation and anonymity**
```
Client → FlareTunnel (local) → Cloudflare Workers → Target Website
```



</div>
**FlareTunnel** is a powerful, unified proxy system that leverages **Cloudflare Workers** to create a robust, rotating proxy network. It allows you to route your traffic through Cloudflare's global edge network, providing high anonymity, speed, and reliability.
## ✨ Features
* **🌐 Unlimited Rotating Proxies**: Automatically deploy and manage multiple Cloudflare Workers as proxy endpoints.
* **🔄 Smart Load Balancing**: Distributes traffic across your workers using Random or Round-Robin strategies.
* **⚡ High Performance**: Uses Cloudflare's global edge network for low latency.
* **🔐 SSL/HTTPS Support**: Full support for HTTPS traffic with optional SSL interception for deep inspection.
* **👥 Multi-Account Support**: seamless management of multiple Cloudflare accounts to maximize request quotas (100k requests/day per account).
* **🛡️ Ad & Tracker Blocking**: Built-in blacklist system to block unwanted traffic and save worker quotas.
* **📊 Analytics**: Real-time usage statistics and quota tracking per account.
## 🏗️ Architecture
```mermaid
graph LR
Client["Client (Browser/App)"] -->|HTTP/HTTPS| LocalProxy["Local Proxy :8080"]
subgraph FlareTunnel System
LocalProxy -->|Load Balancing| Rotator{Worker Rotator}
Rotator -->|Request A| W1[Worker 1]
Rotator -->|Request B| W2[Worker 2]
Rotator -->|Request C| W3[Worker 3]
end
W1 -->|Fetch| Target[Target Website]
W2 -->|Fetch| Target
W3 -->|Fetch| Target
style LocalProxy fill:#7289da,stroke:#333,stroke-width:2px,color:white
style W1 fill:#f38020,stroke:#333,stroke-width:2px,color:white
style W2 fill:#f38020,stroke:#333,stroke-width:2px,color:white
style W3 fill:#f38020,stroke:#333,stroke-width:2px,color:white
style Client fill:#fff,stroke:#333,stroke-width:2px
style Target fill:#fff,stroke:#333,stroke-width:2px
```
## 📦 Installation
### Build from Source
```bash
git clone https://github.com/MorDavid/FlareTunnel.git
cd FlareTunnel
go build -o FlareTunnel FlareTunnel.go
```
## 🚀 Usage
### 1. Configuration
First, set up your Cloudflare credentials. You'll need your Account ID and an API Token (with "Edit Cloudflare Workers" permission).
```bash
./FlareTunnel config
```
### 2. Create Proxies
Deploy new workers to your Cloudflare account.
```bash
# Create 5 new proxy workers
./FlareTunnel create --count 5
```
### 3. Start the Tunnel
Start the local proxy server. By default, it runs on `localhost:8080`.
```bash
./FlareTunnel tunnel
```
Now configure your browser or application to use the proxy:
* **Host**: `127.0.0.1`
* **Port**: `8080`
## 🛠️ Commands Reference
| Command | Description |
|---------|-------------|
| `config` | Configure Cloudflare API credentials (supports multiple accounts) |
| `create` | Deploy new Worker proxies |
| `list` | List all active proxies and show usage stats |
| `tunnel` | Start the local proxy server |
| `test` | Test connectivity of your proxies |
| `cleanup` | Delete all workers from your account |
## 📖 Basic Usage
### Browser Configuration
```
HTTP Proxy: 127.0.0.1:8080
HTTPS Proxy: 127.0.0.1:8080
```
### Python
```python
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
}
r = requests.get("https://httpbin.org/ip",
proxies=proxies,
verify=False)
print(r.json()['origin']) # Cloudflare Worker IP
```
### Quick Test
```bash
./FlareTunnel test
```
---
## 🎯 Common Commands
```bash
# Worker Management
./FlareTunnel list # List all workers
./FlareTunnel list --verbose # Detailed view (created, age, live status)
./FlareTunnel list --status # Check worker response times
./FlareTunnel test # Test workers
./FlareTunnel cleanup # Delete workers from ALL accounts
./FlareTunnel cleanup --account main # Delete workers from 'main' only
# Multi-Account Worker Creation
./FlareTunnel create --count 10 --distribute # Auto-distribute based on quota
./FlareTunnel create --count 5 --account main # Create on specific account
# Configuration Backup & Restore
./FlareTunnel export # Export config (accounts + credentials)
./FlareTunnel import --input config.json # Import config (replace)
./FlareTunnel import --input config.json --merge # Merge with existing
# Tunnel (Proxy Server)
./FlareTunnel tunnel --verbose # Basic
./FlareTunnel tunnel --workers 0-2 # Specific workers
./FlareTunnel tunnel --mode random # Random rotation
# With Blacklist (Recommended!)
./FlareTunnel tunnel --verbose # Default: blacklist-minimal.txt
./FlareTunnel tunnel --blacklist blacklist.txt --verbose
# With Burp Suite
./FlareTunnel tunnel --port 9090 --upstream-proxy http://127.0.0.1:8080 --verbose
```
---
## 💡 Blacklist System
### blacklist-minimal.txt (Default) ⚡
```
✅ Analytics (google-analytics, mixpanel)
✅ Images (.jpg, .png, .gif, etc.)
✅ Fonts (.woff, .ttf, etc.)
✅ Source maps (.map)
Saves: ~30-40% Worker requests
Website: Works perfectly in browser
```
### blacklist.txt (Full) 🔥
```
✅ Everything in minimal
✅ Advertising
✅ Social tracking
✅ CSS/JS files
✅ CDN libraries
Saves: ~60-70% Worker requests
Website: May look broken (missing assets)
```
### blacklist-aggressive.txt (Maximum) 💪
```
✅ Everything in full
✅ Almost everything except HTML/API
Saves: ~80-90% Worker requests
Website: Will break in browser (automation tools only)
```
---
## 🌟 Star History
[](https://www.star-history.com/#MorDavid/FlareTunnel&type=date&legend=top-left)
## ⚠️ Disclaimer
This tool is for educational and research purposes only. Please respect Cloudflare's Terms of Service. The authors are not responsible for any misuse of this tool.
**Made with ❤️ for the security and automation community**
================================================
FILE: blacklist-aggressive.txt
================================================
# FlareTunnel Blacklist - Aggressive Version 🔥💪
# Blocks almost everything except HTML/API requests
# Useful for automation tools (scrapers, bruteforce, etc.)
# Website will look broken in browsers - but you'll save hundreds of thousands of Worker requests!
# ========================================
# 📊 Analytics & Tracking
# ========================================
google-analytics.com
www.google-analytics.com
ssl.google-analytics.com
analytics.google.com
stats.g.doubleclick.net
doubleclick.net
googletagmanager.com
googletagservices.com
facebook.com/tr
connect.facebook.net
graph.facebook.com
px.ads.linkedin.com
analytics.linkedin.com
analytics.tiktok.com
business.tiktok.com
ads.tiktok.com
log.byteoversea.com
hotjar.com
static.hotjar.com
script.hotjar.com
api.mixpanel.com
decide.mixpanel.com
segment.com
cdn.segment.com
api.segment.io
omtrdc.net
adobedc.net
demdex.net
track.hubspot.com
api.hubspot.com
fullstory.com
script.crazyegg.com
js-agent.newrelic.com
bam.nr-data.net
datadoghq-browser-agent.com
browser-intake-datadoghq.com
sentry.io
browser.sentry-cdn.com
criteo.com
taboola.com
outbrain.com
mixpanel.com
amplitude.com
heap.io
logrocket.com
newrelic.com
quantcast.com
clarity.ms
datadoghq.com
pendo.io
mouseflow.com
crazyegg.com
optimizely.com
kissmetrics.com
chartbeat.com
# ========================================
# 📢 Advertising
# ========================================
googlesyndication.com
googleadservices.com
adnxs.com
criteo.com
outbrain.com
taboola.com
media.net
pubmatic.com
rubiconproject.com
adroll.com
adsystem.com
# ========================================
# 🖼️ ALL Images
# ========================================
.jpg
.jpeg
.png
.gif
.webp
.ico
.svg
.bmp
.tiff
.avif
# ========================================
# 🎨 ALL CSS & Fonts
# ========================================
.css
.woff
.woff2
.ttf
.eot
.otf
fonts.googleapis.com
fonts.gstatic.com
# ========================================
# 📦 ALL JavaScript
# ========================================
.js
.map
.min.js
.bundle.js
ajax.googleapis.com
cdnjs.cloudflare.com
cdn.jsdelivr.net
unpkg.com
# ========================================
# 🎬 ALL Media
# ========================================
.mp4
.webm
.ogg
.mp3
.wav
.m4a
# ========================================
# 📄 Documents
# ========================================
.pdf
.zip
.rar
.doc
.docx
.xls
.xlsx
# ========================================
# 👥 Social Media
# ========================================
facebook.com/plugins
twitter.com/widgets
platform.twitter.com
linkedin.com/widgets
instagram.com/embed
pinterest.com
tiktok.com/embed
# ========================================
# 🔌 ALL Widgets
# ========================================
disqus.com
livechatinc.com
tawk.to
intercom.io
drift.com
zendesk.com
/chat
/livechat
/widget
# ========================================
# 🎯 Tracking Paths
# ========================================
/pixel
/track
/analytics
/beacon
/log
/collect
/event
/_ga
/_gat
/gtag
/impression
================================================
FILE: blacklist-minimal.txt
================================================
# ========================================
# 📊 Analytics (Saves the most!)
# ========================================
google-analytics.com
www.google-analytics.com
ssl.google-analytics.com
analytics.google.com
stats.g.doubleclick.net
doubleclick.net
googletagmanager.com
googletagservices.com
facebook.com/tr
connect.facebook.net
graph.facebook.com
px.ads.linkedin.com
analytics.linkedin.com
analytics.tiktok.com
business.tiktok.com
ads.tiktok.com
log.byteoversea.com
hotjar.com
static.hotjar.com
script.hotjar.com
api.mixpanel.com
decide.mixpanel.com
segment.com
cdn.segment.com
api.segment.io
omtrdc.net
adobedc.net
demdex.net
track.hubspot.com
api.hubspot.com
fullstory.com
script.crazyegg.com
js-agent.newrelic.com
bam.nr-data.net
datadoghq-browser-agent.com
browser-intake-datadoghq.com
sentry.io
browser.sentry-cdn.com
criteo.com
taboola.com
outbrain.com
# ========================================
# 🎨 Fonts
# ========================================
fonts.googleapis.com
fonts.gstatic.com
use.typekit.net
use.fontawesome.com
================================================
FILE: blacklist.txt
================================================
# FlareTunnel Blacklist - Full Version 🔥
# Save Worker Requests! 💰
# Each line = one pattern. Lines with # = comments
# Patterns checked against: hostname, path, extension, full URL
# ========================================
# 📊 Analytics & Tracking (Saves a lot!)
# ========================================
google-analytics.com
googletagmanager.com
analytics.google.com
doubleclick.net
stats.g.doubleclick.net
facebook.com/tr
connect.facebook.net
hotjar.com
static.hotjar.com
mixpanel.com
cdn.mxpnl.com
segment.com
cdn.segment.com
amplitude.com
heap.io
fullstory.com
logrocket.com
logrocket.io
newrelic.com
js-agent.newrelic.com
quantcast.com
sentry.io
sentry-cdn.com
clarity.ms
datadoghq.com
pendo.io
mouseflow.com
crazyegg.com
optimizely.com
kissmetrics.com
chartbeat.com
parsely.com
omniconvert.com
# ========================================
# 📢 Advertising & Marketing
# ========================================
googlesyndication.com
googleadservices.com
adservice.google.com
adnxs.com
ads.yahoo.com
advertising.com
adsystem.com
adroll.com
criteo.com
criteo.net
outbrain.com
taboola.com
media.net
yieldmo.com
pubmatic.com
rubiconproject.com
openx.net
indexww.com
contextweb.com
turn.com
adform.net
smartadserver.com
serving-sys.com
adsafeprotected.com
doubleverify.com
moatads.com
# ========================================
# 👥 Social Media Tracking
# ========================================
facebook.com/plugins
twitter.com/widgets
platform.twitter.com
linkedin.com/widgets
platform.linkedin.com
instagram.com/embed
pinterest.com/widgets
snapchat.com/widgets
tiktok.com/embed
reddit.com/static
# ========================================
# 🖼️ Image Formats (Saves a lot!)
# ========================================
.jpg
.jpeg
.png
.gif
.webp
.ico
.svg
.bmp
.tiff
.avif
.jfif
# ========================================
# 🎨 Fonts & CSS
# ========================================
.woff
.woff2
.ttf
.eot
.otf
.css
fonts.googleapis.com
fonts.gstatic.com
use.typekit.net
cloud.typography.com
# ========================================
# 📦 JavaScript & Maps
# ========================================
.js
.map
.min.js
.bundle.js
# ========================================
# 🎬 Media Files (Video/Audio)
# ========================================
.mp4
.webm
.ogg
.mp3
.wav
.m4a
.flac
.aac
# ========================================
# 📄 Documents & Archives
# ========================================
.pdf
.zip
.rar
.7z
.tar
.gz
.doc
.docx
.xls
.xlsx
.ppt
.pptx
# ========================================
# 🎯 Common Tracking Paths
# ========================================
/pixel
/pixel.gif
/track
/tracking
/analytics
/beacon
/log
/collect
/event
/events
/telemetry
/metrics
/stats
/ping
/heartbeat
/_ga
/_gat
/_gid
/gtag
/fbevents
/tr.gif
/impression
/view
/click
# ========================================
# 🌐 CDN & Static Assets
# ========================================
cdnjs.cloudflare.com/ajax/libs
cdn.jsdelivr.net
unpkg.com
stackpath.bootstrapcdn.com
maxcdn.bootstrapcdn.com
ajax.googleapis.com/ajax/libs
# ========================================
# 🔌 Third-Party Widgets
# ========================================
disqus.com
zopim.com
livechatinc.com
tawk.to
intercom.io
drift.com
olark.com
zendesk.com/embeddable
helpscout.com
uservoice.com
# ========================================
# 🎮 Gaming/App Analytics
# ========================================
unity3d.com/stats
unityads.unity3d.com
api.gameanalytics.com
firebase.google.com
# ========================================
# 🛡️ Security/Bot Detection (Careful!)
# ========================================
# Note: Blocking these may break some websites
# recaptcha.net
# google.com/recaptcha
# hcaptcha.com
# cloudflare.com/cdn-cgi
# ========================================
# 🎯 Specific Trackers
# ========================================
bat.bing.com
t.co
bit.ly
ow.ly
goo.gl
tinyurl.com
# ========================================
# 📱 Mobile Analytics
# ========================================
app-measurement.com
firebaselogging.googleapis.com
crashlytics.com
fabric.io
# ========================================
# 💬 Chat Widgets
# ========================================
/chat.js
/livechat
/widget
/embed.js
# ========================================
# ⚙️ Add your own patterns here:
# ========================================
# Examples:
# example-tracker.com
# .mp4
# /my-custom-tracking-path
================================================
FILE: build.bat
================================================
@echo off
REM Build script for FlareTunnel Go version (Windows)
echo 🔨 Building FlareTunnel...
echo.
echo 📦 Downloading dependencies...
go mod download
if errorlevel 1 (
echo ❌ Failed to download dependencies
pause
exit /b 1
)
echo.
echo 🏗️ Building for Windows...
go build -ldflags="-s -w" -o flaretunnel.exe flaretunnel.go
if errorlevel 1 (
echo ❌ Build failed
pause
exit /b 1
)
echo.
echo ✅ Build complete: flaretunnel.exe
echo.
echo 🚀 Quick start:
echo flaretunnel.exe config # Configure accounts
echo flaretunnel.exe create --count 5 # Create workers
echo flaretunnel.exe list --verbose # List workers
echo flaretunnel.exe tunnel --verbose # Start proxy
echo.
set /p REPLY="Build for other platforms? (y/N) "
if /i "%REPLY%"=="y" (
echo.
echo 🌍 Building for multiple platforms...
echo Building for Linux (amd64)...
set GOOS=linux
set GOARCH=amd64
go build -ldflags="-s -w" -o flaretunnel-linux-amd64 flaretunnel.go
echo Building for macOS (amd64)...
set GOOS=darwin
set GOARCH=amd64
go build -ldflags="-s -w" -o flaretunnel-macos-amd64 flaretunnel.go
echo Building for macOS (arm64)...
set GOOS=darwin
set GOARCH=arm64
go build -ldflags="-s -w" -o flaretunnel-macos-arm64 flaretunnel.go
echo.
echo ✅ Cross-compilation complete!
echo.
echo 📦 Built binaries:
dir /b flaretunnel-*
echo.
)
echo ✨ Done!
pause
================================================
FILE: build.sh
================================================
#!/bin/bash
# Build script for FlareTunnel Go version
set -e
echo "🔨 Building FlareTunnel..."
# Get dependencies
echo "📦 Downloading dependencies..."
go mod download
# Build for current platform
echo "🏗️ Building for current platform..."
go build -ldflags="-s -w" -o flaretunnel flaretunnel.go
echo "✅ Build complete: ./flaretunnel"
echo ""
echo "📊 Binary size:"
ls -lh flaretunnel | awk '{print $5}'
echo ""
echo "🚀 Quick start:"
echo " ./flaretunnel config # Configure accounts"
echo " ./flaretunnel create --count 5 # Create workers"
echo " ./flaretunnel list --verbose # List workers"
echo " ./flaretunnel tunnel --verbose # Start proxy"
echo ""
# Optional: Build for other platforms
read -p "Build for other platforms? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
echo "🌍 Building for multiple platforms..."
# Windows
echo " Building for Windows (amd64)..."
GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o flaretunnel-windows-amd64.exe flaretunnel.go
# Linux
echo " Building for Linux (amd64)..."
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o flaretunnel-linux-amd64 flaretunnel.go
# macOS Intel
echo " Building for macOS (amd64)..."
GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o flaretunnel-macos-amd64 flaretunnel.go
# macOS Apple Silicon
echo " Building for macOS (arm64)..."
GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o flaretunnel-macos-arm64 flaretunnel.go
# Linux ARM (Raspberry Pi, etc.)
echo " Building for Linux (arm64)..."
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o flaretunnel-linux-arm64 flaretunnel.go
echo ""
echo "✅ Cross-compilation complete!"
echo ""
echo "📦 Built binaries:"
ls -lh flaretunnel-* | awk '{print " " $9 " - " $5}'
echo ""
fi
echo "✨ Done!"
================================================
FILE: example_usage.py
================================================
#!/usr/bin/env python3
"""
FlareTunnel - Quick Usage Example
Make sure proxy is running: python FlareTunnel.py tunnel --verbose
"""
import requests
import urllib3
import time
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# FlareTunnel proxy configuration
proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
}
print("=" * 80)
print("FlareTunnel - Usage Example")
print("=" * 80)
print()
print("Make sure proxy is running: python FlareTunnel.py tunnel --verbose")
print()
print("=" * 80)
print()
# Test 1: Simple GET
print("📡 Test 1: GET Request")
print("-" * 40)
try:
response = requests.get(
"https://httpbin.org/get?test=123",
proxies=proxies,
verify=False,
timeout=10
)
print(f"✓ Status: {response.status_code}")
data = response.json()
print(f"✓ Origin IP: {data.get('origin', 'N/A')}")
print(f"✓ Args: {data.get('args', {})}")
print()
except Exception as e:
print(f"✗ Error: {e}")
print()
# Test 2: POST with JSON
print("📤 Test 2: POST Request with JSON")
print("-" * 40)
try:
response = requests.post(
"https://httpbin.org/post",
json={
"username": "testuser",
"action": "login",
"timestamp": time.time()
},
proxies=proxies,
verify=False,
timeout=10
)
print(f"✓ Status: {response.status_code}")
data = response.json()
print(f"✓ Received JSON: {data.get('json', {})}")
print()
except Exception as e:
print(f"✗ Error: {e}")
print()
# Test 3: Custom Headers
print("📋 Test 3: Custom Headers")
print("-" * 40)
try:
response = requests.get(
"https://httpbin.org/headers",
headers={
"X-Custom-Header": "FlareTunnel-Test",
"User-Agent": "FlareTunnel/2.0"
},
proxies=proxies,
verify=False,
timeout=10
)
print(f"✓ Status: {response.status_code}")
data = response.json()
headers_received = data.get('headers', {})
print(f"✓ Custom Header: {headers_received.get('X-Custom-Header', 'Not received')}")
print(f"✓ User-Agent: {headers_received.get('User-Agent', 'Not received')}")
print()
except Exception as e:
print(f"✗ Error: {e}")
print()
# Test 4: IP Rotation Check
print("🔄 Test 4: IP Rotation (5 requests)")
print("-" * 40)
ips = set()
for i in range(5):
try:
response = requests.get(
"https://httpbin.org/ip",
proxies=proxies,
verify=False,
timeout=10
)
if response.status_code == 200:
ip = response.json().get('origin', 'N/A')
ips.add(ip)
print(f" Request {i+1}: {ip}")
time.sleep(0.5)
except Exception as e:
print(f" Request {i+1}: Error - {e}")
print(f"\n✓ Unique IPs: {len(ips)}")
for ip in sorted(ips):
print(f" - {ip}")
print()
# Test 5: Real Website
print("🌐 Test 5: Real Website (example.com)")
print("-" * 40)
try:
response = requests.get(
"https://example.com",
proxies=proxies,
verify=False,
timeout=10
)
print(f"✓ Status: {response.status_code}")
print(f"✓ Size: {len(response.content):,} bytes")
# Show first 100 chars
content_preview = response.text[:100].replace('\n', ' ')
print(f"✓ Preview: {content_preview}...")
print()
except Exception as e:
print(f"✗ Error: {e}")
print()
# Summary
print("=" * 80)
print("✅ All tests completed!")
print("=" * 80)
print()
print("💡 Key Points:")
print(" • All requests went through Cloudflare Workers")
print(" • Use rotation modes to change IPs (--mode random)")
print(" • Use blacklist to save Worker requests (default: blacklist-minimal.txt)")
print(" • IP addresses are blocked by default (Cloudflare doesn't support them)")
print()
print("📚 Learn More:")
print(" • README.md - Full documentation")
print(" • python FlareTunnel.py --help - All commands")
print()
================================================
FILE: go.mod
================================================
module flaretunnel
go 1.21
require github.com/olekukonko/tablewriter v0.0.5
require github.com/mattn/go-runewidth v0.0.9 // indirect
================================================
FILE: go.sum
================================================
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
gitextract_pkuk33xv/ ├── .github/ │ └── FUNDING.yml ├── FlareTunnel.go ├── README.md ├── blacklist-aggressive.txt ├── blacklist-minimal.txt ├── blacklist.txt ├── build.bat ├── build.sh ├── example_usage.py ├── go.mod └── go.sum
SYMBOL INDEX (54 symbols across 1 files)
FILE: FlareTunnel.go
constant Version (line 34) | Version = "1.0.0"
constant CloudflareBaseURL (line 35) | CloudflareBaseURL = "https://api.cloudflare.com/client/v4"
constant WorkerScript (line 36) | WorkerScript = `/**
type Account (line 177) | type Account struct
type Config (line 185) | type Config struct
type Worker (line 190) | type Worker struct
type Analytics (line 200) | type Analytics struct
type CloudflareClient (line 212) | type CloudflareClient struct
method GetSubdomain (line 232) | func (c *CloudflareClient) GetSubdomain() (string, error) {
method CreateWorker (line 273) | func (c *CloudflareClient) CreateWorker(name string) (*Worker, error) {
method ListWorkers (line 335) | func (c *CloudflareClient) ListWorkers() ([]*Worker, error) {
method DeleteWorker (line 381) | func (c *CloudflareClient) DeleteWorker(name string) error {
method GetAnalytics (line 406) | func (c *CloudflareClient) GetAnalytics() (*Analytics, error) {
function NewCloudflareClient (line 220) | func NewCloudflareClient(apiToken, accountID string) *CloudflareClient {
function generateCACert (line 514) | func generateCACert(certPath, keyPath string) error {
function generateHostCert (line 571) | func generateHostCert(hostname, caCertPath, caKeyPath string) (*tls.Cert...
type FlareTunnel (line 641) | type FlareTunnel struct
method SaveEndpoints (line 693) | func (ft *FlareTunnel) SaveEndpoints(workers []*Worker) error {
method LoadEndpoints (line 702) | func (ft *FlareTunnel) LoadEndpoints() ([]*Worker, error) {
method SyncEndpoints (line 721) | func (ft *FlareTunnel) SyncEndpoints() ([]*Worker, error) {
method CreateWorkers (line 743) | func (ft *FlareTunnel) CreateWorkers(count int, accountName string, di...
method ListWorkers (line 861) | func (ft *FlareTunnel) ListWorkers(verbose, checkStatus bool) error {
method CleanupWorkers (line 979) | func (ft *FlareTunnel) CleanupWorkers(accountName string) error {
method TestWorkers (line 1018) | func (ft *FlareTunnel) TestWorkers(targetURL, method string) error {
method ExportConfig (line 1088) | func (ft *FlareTunnel) ExportConfig(outputFile string) error {
method ImportConfig (line 1129) | func (ft *FlareTunnel) ImportConfig(inputFile string, merge bool) error {
function NewFlareTunnel (line 650) | func NewFlareTunnel(configFile string) (*FlareTunnel, error) {
function loadConfig (line 674) | func loadConfig(path string) (*Config, error) {
function min (line 1218) | func min(a, b int) int {
function fileExists (line 1225) | func fileExists(path string) bool {
type ProxyServer (line 1234) | type ProxyServer struct
method LoadWorkers (line 1266) | func (ps *ProxyServer) LoadWorkers(endpointsFile string, workerIndices...
method LoadBlacklist (line 1292) | func (ps *ProxyServer) LoadBlacklist(blacklistFile string) error {
method GetWorkerURL (line 1328) | func (ps *ProxyServer) GetWorkerURL() string {
method IsBlacklisted (line 1347) | func (ps *ProxyServer) IsBlacklisted(targetURL string) bool {
method HandleHTTP (line 1380) | func (ps *ProxyServer) HandleHTTP(w http.ResponseWriter, r *http.Reque...
method HandleCONNECT (line 1474) | func (ps *ProxyServer) HandleCONNECT(w http.ResponseWriter, r *http.Re...
method Start (line 1624) | func (ps *ProxyServer) Start(blacklistFile string) error {
function NewProxyServer (line 1256) | func NewProxyServer(host string, port int) *ProxyServer {
function generateWorkerName (line 1738) | func generateWorkerName() string {
function randomString (line 1744) | func randomString(n int) string {
function isIPAddress (line 1754) | func isIPAddress(hostname string) bool {
function testWorker (line 1760) | func testWorker(workerURL string) bool {
function max (line 1771) | func max(a, b int) int {
function sumMap (line 1778) | func sumMap(m map[string]int) int {
type simpleMultipartWriter (line 1787) | type simpleMultipartWriter struct
method WriteField (line 1799) | func (w *simpleMultipartWriter) WriteField(field, value string) {
method Close (line 1805) | func (w *simpleMultipartWriter) Close() {
method FormDataContentType (line 1809) | func (w *simpleMultipartWriter) FormDataContentType() string {
function multipartWriter (line 1792) | func multipartWriter(buf *bytes.Buffer) *simpleMultipartWriter {
function setupConfig (line 1817) | func setupConfig() error {
function parseWorkerIndices (line 1918) | func parseWorkerIndices(indicesStr string) []int {
function main (line 1949) | func main() {
function printHelp (line 2221) | func printHelp() {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (94K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 780,
"preview": "# These are supported funding model platforms\n\ngithub: mordavid\npatreon: mordavid\nopen_collective: # Replace with a sing"
},
{
"path": "FlareTunnel.go",
"chars": 59064,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t"
},
{
"path": "README.md",
"chars": 6846,
"preview": "# 🚀 FlareTunnel\r\n\r\n<div align=\"center\">\r\n\r\n<img src=\"logo.png\" alt=\"FlareTunnel\" width=\"300\">\r\n\r\n**A unified proxy syste"
},
{
"path": "blacklist-aggressive.txt",
"chars": 3162,
"preview": "# FlareTunnel Blacklist - Aggressive Version 🔥💪\r\n# Blocks almost everything except HTML/API requests\r\n# Useful for autom"
},
{
"path": "blacklist-minimal.txt",
"chars": 1085,
"preview": "# ========================================\r\n# 📊 Analytics (Saves the most!)\r\n# ========================================\r"
},
{
"path": "blacklist.txt",
"chars": 4634,
"preview": "# FlareTunnel Blacklist - Full Version 🔥\r\n# Save Worker Requests! 💰\r\n# Each line = one pattern. Lines with # = comments\r"
},
{
"path": "build.bat",
"chars": 1544,
"preview": "@echo off\r\nREM Build script for FlareTunnel Go version (Windows)\r\n\r\necho 🔨 Building FlareTunnel...\r\necho.\r\n\r\necho 📦 Down"
},
{
"path": "build.sh",
"chars": 1880,
"preview": "#!/bin/bash\n# Build script for FlareTunnel Go version\n\nset -e\n\necho \"🔨 Building FlareTunnel...\"\n\n# Get dependencies\necho"
},
{
"path": "example_usage.py",
"chars": 4174,
"preview": "#!/usr/bin/env python3\r\n\"\"\"\r\nFlareTunnel - Quick Usage Example\r\nMake sure proxy is running: python FlareTunnel.py tunnel"
},
{
"path": "go.mod",
"chars": 136,
"preview": "module flaretunnel\n\ngo 1.21\n\nrequire github.com/olekukonko/tablewriter v0.0.5\n\nrequire github.com/mattn/go-runewidth v0."
},
{
"path": "go.sum",
"chars": 362,
"preview": "github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0."
}
]
About this extraction
This page contains the full source code of the MorDavid/FlareTunnel GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (81.7 KB), approximately 22.9k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.