mirror of
https://github.com/SajadMRjl/find-me-internet.git
synced 2026-07-02 15:09:00 +00:00
feat: add all internals and cmd
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# --- Binaries & Build Results ---
|
||||
# Binaries built by 'go build'
|
||||
scanner
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
|
||||
BIN
bin/sing-box
Executable file
BIN
bin/sing-box
Executable file
Binary file not shown.
95
cmd/main.go
Normal file
95
cmd/main.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"find-me-internet/internal/filter"
|
||||
"find-me-internet/internal/parser"
|
||||
"find-me-internet/internal/tester"
|
||||
)
|
||||
|
||||
const (
|
||||
SingBoxPath = "./bin/sing-box" // Make sure this exists!
|
||||
TestTarget = "http://cp.cloudflare.com"
|
||||
MaxWorkers = 10
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 0. Setup
|
||||
// Check if binary exists
|
||||
if _, err := os.Stat(SingBoxPath); os.IsNotExist(err) {
|
||||
fmt.Printf("Error: sing-box binary not found at %s\n", SingBoxPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Mock Input Data (Replace with file reading logic later)
|
||||
rawLinks := []string{
|
||||
"vless://4525c260-df3c-4f62-b8f1-f4f5f305694b@66.81.247.155:443?encryption=none&security=tls&sni=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net&fp=chrome&insecure=0&allowInsecure=0&type=ws&host=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net&path=%2F%3Fed#%DA%86%D9%86%D9%84%20%D8%AA%D9%84%DA%AF%D8%B1%D8%A7%D9%85%20%3A%20%40CroSs_Guildd%F0%9F%92%8A",
|
||||
"vless://efdb2890-6dd7-4e65-8984-f0b1d3ae4e01@here-we-go-again.embeddedonline.org:443?encryption=none&security=tls&sni=here-we-go-again.embeddedonline.org&fp=chrome&alpn=http%2F1.1&insecure=0&allowInsecure=0&type=ws&host=here-we-go-again.embeddedonline.org&path=%2FJ1jTS0GMxqS0Atmd5x#here-we-go-again.embeddedonline.org%20tls%20WS%20direct%20vless",
|
||||
// Add more links here...
|
||||
}
|
||||
|
||||
fmt.Printf("Loaded %d links. Starting scan...\n", len(rawLinks))
|
||||
|
||||
// Pipelines
|
||||
netFilter := filter.NewPipeline(2 * time.Second)
|
||||
boxRunner := tester.NewRunner(SingBoxPath, TestTarget, 5*time.Second)
|
||||
|
||||
// Concurrency Controls
|
||||
var wg sync.WaitGroup
|
||||
semaphore := make(chan struct{}, MaxWorkers) // Limit active Sing-box instances
|
||||
|
||||
// Results
|
||||
successCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
startTotal := time.Now()
|
||||
|
||||
for _, link := range rawLinks {
|
||||
wg.Add(1)
|
||||
|
||||
go func(raw string) {
|
||||
defer wg.Done()
|
||||
|
||||
// --- STAGE 1: PARSE ---
|
||||
proxy, err := parser.ParseLink(raw)
|
||||
if err != nil {
|
||||
// fmt.Printf("Invalid Link: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// --- STAGE 2: CHEAP FILTER ---
|
||||
// Check TCP and TLS Handshake first
|
||||
if !netFilter.Check(proxy) {
|
||||
// fmt.Printf("[DEAD] %s:%d\n", proxy.Address, proxy.Port)
|
||||
return
|
||||
}
|
||||
|
||||
// --- STAGE 3: EXPENSIVE TEST ---
|
||||
semaphore <- struct{}{} // Acquire worker slot
|
||||
err = boxRunner.Test(proxy)
|
||||
<-semaphore // Release worker slot
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("[FAIL] %s (%v)\n", proxy.SNI, err)
|
||||
return
|
||||
}
|
||||
|
||||
// --- SUCCESS ---
|
||||
mu.Lock()
|
||||
successCount++
|
||||
mu.Unlock()
|
||||
|
||||
fmt.Printf("✅ [OK] %s | Latency: %dms | Type: %s\n",
|
||||
proxy.Address, proxy.Latency.Milliseconds(), proxy.Type)
|
||||
|
||||
}(link)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
fmt.Printf("\n--- Scan Complete in %s ---\n", time.Since(startTotal))
|
||||
fmt.Printf("Valid Proxies Found: %d\n", successCount)
|
||||
}
|
||||
7
go.mod
7
go.mod
@@ -2,6 +2,8 @@ module find-me-internet
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require github.com/gvcgo/vpnparser v0.2.7
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.1.1 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
@@ -11,12 +13,10 @@ require (
|
||||
github.com/charmbracelet/lipgloss v0.8.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gogf/gf/v2 v2.6.1 // indirect
|
||||
github.com/gookit/color v1.5.3 // indirect
|
||||
github.com/gvcgo/goutils v0.8.5 // indirect
|
||||
github.com/gvcgo/vpnparser v0.2.7 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
@@ -26,10 +26,7 @@ require (
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/pterm/pterm v0.12.62 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.25.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.opentelemetry.io/otel v1.15.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.15.1 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
|
||||
44
go.sum
44
go.sum
@@ -1,3 +1,5 @@
|
||||
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
|
||||
atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=
|
||||
atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4=
|
||||
atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
|
||||
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
|
||||
@@ -13,6 +15,8 @@ github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSr
|
||||
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
|
||||
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
|
||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
@@ -23,18 +27,29 @@ github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gogf/gf/v2 v2.6.1 h1:n/cfXM506WjhPa6Z1CEDuHNM1XZ7C8JzSDPn2AfuxgQ=
|
||||
github.com/gogf/gf/v2 v2.6.1/go.mod h1:x2XONYcI4hRQ/4gMNbWHmZrNzSEIg20s2NULbzom5k0=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
|
||||
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
|
||||
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/gvcgo/goutils v0.8.5 h1:0QPLOl5lfjV9vIAUnB5MPze5YpfMkrnxUW512ZjH82Q=
|
||||
github.com/gvcgo/goutils v0.8.5/go.mod h1:g/gPJxbpSiLK0q8a1qpkaf6ec3LOcxb7jj1ekDsKqzY=
|
||||
github.com/gvcgo/vpnparser v0.2.7 h1:+uezF5c00ROSKsD6a3ysmx3BdHxzI1ICkzuWeCHDQww=
|
||||
@@ -42,8 +57,12 @@ github.com/gvcgo/vpnparser v0.2.7/go.mod h1:JQwo6gDtzYAQgO0o63FvP0db/eO+7QKy3TnT
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
@@ -51,6 +70,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
@@ -61,6 +82,9 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||
@@ -75,33 +99,36 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
|
||||
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
|
||||
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
|
||||
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
|
||||
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -139,6 +166,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -2,9 +2,11 @@ package filter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"find-me-internet/internal/model"
|
||||
"net"
|
||||
"time"
|
||||
"strconv"
|
||||
|
||||
"find-me-internet/internal/model"
|
||||
)
|
||||
|
||||
// Pipeline represents the filter configuration
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"find-me-internet/internal/model"
|
||||
@@ -11,44 +10,61 @@ import (
|
||||
"github.com/gvcgo/vpnparser/pkgs/outbound"
|
||||
)
|
||||
|
||||
// ParseLink converts a raw proxy string (vless://, vmess://) into our internal Model
|
||||
// tempConfig allows us to extract deep fields from the Sing-box JSON
|
||||
type tempConfig struct {
|
||||
Transport struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"transport"`
|
||||
TLS struct {
|
||||
ServerName string `json:"server_name"`
|
||||
} `json:"tls"`
|
||||
}
|
||||
|
||||
func ParseLink(raw string) (*model.Proxy, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("empty link")
|
||||
}
|
||||
|
||||
// 1. Use vpnparser to decode the link
|
||||
item, err := outbound.ParseRawUriToProxyItem(raw, outbound.ClientTypeSingBox)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse failed: %w", err)
|
||||
}
|
||||
// 1. Parse Raw Link
|
||||
// We omit the second argument to let the library use default parsing
|
||||
item := outbound.ParseRawUriToProxyItem(raw)
|
||||
if item == nil {
|
||||
return nil, fmt.Errorf("unknown protocol or invalid link")
|
||||
}
|
||||
|
||||
// 2. Map to our Internal Model
|
||||
// The library returns a ProxyItem struct. We extract what we need for the "Cheap Checks".
|
||||
// 2. Initialize Proxy Model
|
||||
p := &model.Proxy{
|
||||
RawLink: raw,
|
||||
Address: item.Address,
|
||||
Port: item.Port,
|
||||
Network: item.Network,
|
||||
SNI: item.Sni,
|
||||
}
|
||||
|
||||
// 3. Determine Protocol (The library stores this in Protocol field)
|
||||
switch strings.ToLower(item.Protocol) {
|
||||
// 3. Extract SNI and Network from the Outbound JSON
|
||||
// The library packs the details into 'item.Outbound' string
|
||||
if item.Outbound != "" {
|
||||
var cfg tempConfig
|
||||
if err := json.Unmarshal([]byte(item.Outbound), &cfg); err == nil {
|
||||
p.Network = cfg.Transport.Type
|
||||
p.SNI = cfg.TLS.ServerName
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: If JSON extraction failed but we have a generic "Host" (sometimes used as SNI)
|
||||
// Note: 'item.Host' doesn't exist either, so we rely solely on JSON extraction above.
|
||||
|
||||
// 4. Map Protocol (Library uses 'Scheme')
|
||||
switch strings.ToLower(item.Scheme) {
|
||||
case "vless":
|
||||
p.Type = model.TypeVLESS
|
||||
if strings.Contains(raw, "reality") {
|
||||
p.Type = model.TypeVLESS // We treat Reality as VLESS with special TLS options
|
||||
p.Type = model.TypeVLESS // Reality is technically VLESS
|
||||
}
|
||||
case "vmess":
|
||||
p.Type = model.TypeVMess
|
||||
case "trojan":
|
||||
p.Type = model.TypeTrojan
|
||||
case "shadowsocks":
|
||||
case "shadowsocks", "ss":
|
||||
p.Type = model.TypeShadowsocks
|
||||
default:
|
||||
p.Type = model.TypeUnknown
|
||||
|
||||
70
internal/tester/config.go
Normal file
70
internal/tester/config.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package tester
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"find-me-internet/internal/model"
|
||||
|
||||
"github.com/gvcgo/vpnparser/pkgs/outbound"
|
||||
)
|
||||
|
||||
// SingBoxConfig is the minimal structure Sing-box expects
|
||||
type SingBoxConfig struct {
|
||||
Log LogConfig `json:"log"`
|
||||
Inbounds []InboundConfig `json:"inbounds"`
|
||||
Outbounds []interface{} `json:"outbounds"` // Interface because structure varies
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `json:"level"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type InboundConfig struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag"`
|
||||
Listen string `json:"listen"`
|
||||
ListenPort int `json:"listen_port"`
|
||||
}
|
||||
|
||||
// GenerateConfig creates a JSON config string for Sing-box
|
||||
func GenerateConfig(p *model.Proxy, localPort int) ([]byte, error) {
|
||||
// 1. Convert the Raw Link directly to Sing-box Outbound Object
|
||||
item := outbound.ParseRawUriToProxyItem(p.RawLink, outbound.SingBox)
|
||||
if item == nil {
|
||||
return nil, fmt.Errorf("failed to parse link for config generation")
|
||||
}
|
||||
|
||||
outboundJsonStr := item.GetOutbound()
|
||||
var sbOutbound interface{}
|
||||
if err := json.Unmarshal([]byte(outboundJsonStr), &sbOutbound); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse sing-box outbound json: %w", err)
|
||||
}
|
||||
|
||||
// 2. Wrap it in the full config structure
|
||||
config := SingBoxConfig{
|
||||
Log: LogConfig{
|
||||
Level: "panic", // Silence all logs to keep console clean
|
||||
Disabled: true,
|
||||
},
|
||||
Inbounds: []InboundConfig{
|
||||
{
|
||||
Type: "mixed", // Supports both SOCKS5 and HTTP
|
||||
Tag: "in-local",
|
||||
Listen: "127.0.0.1",
|
||||
ListenPort: localPort,
|
||||
},
|
||||
},
|
||||
Outbounds: []interface{}{
|
||||
sbOutbound, // The Proxy being tested
|
||||
map[string]string{
|
||||
"type": "direct",
|
||||
"tag": "direct",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return json.MarshalIndent(config, "", " ")
|
||||
}
|
||||
143
internal/tester/runner.go
Normal file
143
internal/tester/runner.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package tester
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"find-me-internet/internal/model"
|
||||
)
|
||||
|
||||
// Runner handles the execution of Sing-box
|
||||
type Runner struct {
|
||||
BinPath string
|
||||
TestURL string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func NewRunner(binPath string, testURL string, timeout time.Duration) *Runner {
|
||||
return &Runner{
|
||||
BinPath: binPath,
|
||||
TestURL: testURL,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Test performs the full latency check
|
||||
func (r *Runner) Test(p *model.Proxy) error {
|
||||
// 1. Get a random free port
|
||||
port, err := getFreePort()
|
||||
if err != nil {
|
||||
return fmt.Errorf("no free ports: %v", err)
|
||||
}
|
||||
|
||||
// 2. Generate Config
|
||||
configData, err := GenerateConfig(p, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Write Config to Temp File
|
||||
// specific name helps debugging if needed: config_<port>.json
|
||||
configName := filepath.Join(os.TempDir(), fmt.Sprintf("sb_config_%d.json", port))
|
||||
if err := os.WriteFile(configName, configData, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(configName) // Cleanup after test
|
||||
|
||||
// 4. Start Sing-box Process
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.Timeout+2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, r.BinPath, "run", "-c", configName)
|
||||
// cmd.Stdout = os.Stdout // Uncomment for debugging
|
||||
// cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start sing-box: %v", err)
|
||||
}
|
||||
|
||||
// Ensure process is killed when function exits
|
||||
defer func() {
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
// 5. Wait for Sing-box to initialize
|
||||
// A smart retry loop is better than a fixed sleep
|
||||
proxyReady := waitForPort(port, 2*time.Second)
|
||||
if !proxyReady {
|
||||
return fmt.Errorf("sing-box did not start in time")
|
||||
}
|
||||
|
||||
// 6. Perform HTTP Latency Test
|
||||
latency, err := r.measureLatency(port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7. Success! Update the model
|
||||
p.Latency = latency
|
||||
return nil
|
||||
}
|
||||
|
||||
// measureLatency makes the actual HTTP request
|
||||
func (r *Runner) measureLatency(port int) (time.Duration, error) {
|
||||
proxyUrl, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", port))
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyUrl),
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
Timeout: r.Timeout,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := client.Get(r.TestURL)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check for valid response codes (200 OK or 204 No Content)
|
||||
if resp.StatusCode != 200 && resp.StatusCode != 204 {
|
||||
return 0, fmt.Errorf("bad status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return time.Since(start), nil
|
||||
}
|
||||
|
||||
// Helpers
|
||||
func getFreePort() (int, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer l.Close()
|
||||
return l.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
|
||||
func waitForPort(port int, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), 100*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user