mirror of
https://github.com/SajadMRjl/find-me-internet.git
synced 2026-07-02 15:09:00 +00:00
feat: add alive configs, store testing output
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ scanner
|
|||||||
*.dylib
|
*.dylib
|
||||||
data/*
|
data/*
|
||||||
!data/valid_proxies.txt
|
!data/valid_proxies.txt
|
||||||
|
!data/alive_proxies.txt
|
||||||
bin/
|
bin/
|
||||||
bin/*
|
bin/*
|
||||||
|
|
||||||
|
|||||||
140
cmd/main.go
140
cmd/main.go
@@ -19,123 +19,103 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 1. Init
|
// 1. Init & Config
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
logger.Setup(cfg.LogLevel)
|
logger.Setup(cfg.LogLevel)
|
||||||
|
if len(os.Args) > 1 { cfg.InputPath = os.Args[1] }
|
||||||
|
|
||||||
// CLI Argument Override
|
// 2. Writers (Valid, Alive, Dataset)
|
||||||
// Usage: ./find-me-internet [OPTIONAL_INPUT_SOURCE]
|
validJson, _ := sink.NewJSONL(cfg.OutputPath)
|
||||||
if len(os.Args) > 1 {
|
defer validJson.Close()
|
||||||
cfg.InputPath = os.Args[1]
|
validTxt, _ := sink.NewText(cfg.TxtOutputPath)
|
||||||
slog.Info("input_source_overridden", "source", cfg.InputPath)
|
defer validTxt.Close()
|
||||||
}
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
aliveJson, _ := sink.NewJSONL(cfg.AliveOutputPath)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
defer aliveJson.Close()
|
||||||
|
aliveTxt, _ := sink.NewText(cfg.AliveTxtOutputPath)
|
||||||
|
defer aliveTxt.Close()
|
||||||
|
|
||||||
// 2. Services
|
datasetWriter, _ := sink.NewJSONL(cfg.DatasetOutputPath)
|
||||||
geoDB, err := geoip.Open(cfg.GeoIPPath)
|
defer datasetWriter.Close()
|
||||||
if err != nil {
|
|
||||||
slog.Warn("geoip_db_missing", "error", err)
|
|
||||||
} else {
|
|
||||||
defer geoDB.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonWriter, err := sink.NewJSONL(cfg.OutputPath)
|
// 3. Services
|
||||||
if err != nil {
|
geoDB, _ := geoip.Open(cfg.GeoIPPath)
|
||||||
slog.Error("cannot_create_json_output", "error", err)
|
if geoDB != nil { defer geoDB.Close() }
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer jsonWriter.Close()
|
|
||||||
|
|
||||||
txtWriter, err := sink.NewText(cfg.TxtOutputPath)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("cannot_create_txt_output", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer txtWriter.Close()
|
|
||||||
|
|
||||||
deduplicator := dedup.New()
|
deduplicator := dedup.New()
|
||||||
netFilter := filter.NewPipeline(cfg.TcpTimeout)
|
netFilter := filter.NewPipeline(cfg.TcpTimeout)
|
||||||
boxRunner := tester.NewRunner(cfg.SingBoxPath, cfg.TestURL, cfg.TestTimeout)
|
boxRunner := tester.NewRunner(cfg.SingBoxPath, cfg.TestURL, cfg.TestTimeout)
|
||||||
|
|
||||||
// 3. Input Stream (Smart Load)
|
// 4. Input Stream
|
||||||
// Supports both http://... and ./path/to/file.txt
|
|
||||||
linkStream, err := source.Load(cfg.InputPath)
|
linkStream, err := source.Load(cfg.InputPath)
|
||||||
if err != nil {
|
if err != nil { slog.Error("input_failed", "err", err); os.Exit(1) }
|
||||||
slog.Error("input_source_failed", "error", err, "path", cfg.InputPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Worker Pool
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
semaphore := make(chan struct{}, cfg.Workers)
|
semaphore := make(chan struct{}, cfg.Workers)
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
countProcessed := 0
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
countValid := 0
|
|
||||||
var mu sync.Mutex
|
|
||||||
|
|
||||||
slog.Info("pipeline_started", "workers", cfg.Workers)
|
slog.Info("pipeline_started", "workers", cfg.Workers)
|
||||||
|
|
||||||
// Main Loop
|
|
||||||
loop:
|
|
||||||
for rawLink := range linkStream {
|
for rawLink := range linkStream {
|
||||||
select {
|
select {
|
||||||
case <-sigChan:
|
case <-sigChan:
|
||||||
slog.Info("shutdown_signal_received", "msg", "finishing pending jobs...")
|
goto cleanup
|
||||||
break loop
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(raw string) {
|
go func(raw string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
// A. Parse
|
// STEP 1: PARSE
|
||||||
proxy, err := parser.ParseLink(raw)
|
proxy, err := parser.ParseLink(raw)
|
||||||
if err != nil { return }
|
if err != nil { return } // Cannot track unparseable junk
|
||||||
|
|
||||||
// B. Dedup
|
// STEP 2: DEDUP
|
||||||
if deduplicator.Seen(proxy.Address, proxy.Port) { return }
|
if deduplicator.Seen(proxy) { return }
|
||||||
|
|
||||||
// C. Filter
|
// STEP 3: ENRICH (Country)
|
||||||
if !netFilter.Check(proxy) { return }
|
// We do this EARLY so even "Dead" proxies in the dataset have a Country label
|
||||||
|
|
||||||
// D. Test
|
|
||||||
semaphore <- struct{}{}
|
|
||||||
err = boxRunner.Test(proxy)
|
|
||||||
<-semaphore
|
|
||||||
|
|
||||||
if err != nil { return }
|
|
||||||
|
|
||||||
// E. Enrich
|
|
||||||
if geoDB != nil {
|
if geoDB != nil {
|
||||||
proxy.Country = geoDB.Lookup(proxy.Address)
|
proxy.Country = geoDB.Lookup(proxy.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// F. Save
|
// STEP 4: FILTER (Sets p.Status, p.FailureReason if fails)
|
||||||
jsonWriter.Write(proxy)
|
if !netFilter.Check(proxy) {
|
||||||
txtWriter.Write(proxy)
|
// Proxy is DEAD. The Filter has already set:
|
||||||
|
// p.Status = "dead"
|
||||||
|
// p.FailureReason = "tcp_timeout" (etc)
|
||||||
|
datasetWriter.Write(proxy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Stats
|
// STEP 5: TEST (Sets p.Status, p.FailureReason if fails)
|
||||||
mu.Lock()
|
semaphore <- struct{}{}
|
||||||
countValid++
|
err = boxRunner.Test(proxy)
|
||||||
mu.Unlock()
|
<-semaphore
|
||||||
|
|
||||||
slog.Info("proxy_saved",
|
if err != nil {
|
||||||
"country", proxy.Country,
|
// Proxy is ALIVE (Semi-working). Runner has already set:
|
||||||
"latency", proxy.Latency.Milliseconds(),
|
// p.Status = "alive"
|
||||||
"type", proxy.Type,
|
// p.FailureReason = "http_error_502" (etc)
|
||||||
)
|
|
||||||
|
aliveJson.Write(proxy)
|
||||||
|
aliveTxt.Write(proxy)
|
||||||
|
datasetWriter.Write(proxy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validJson.Write(proxy)
|
||||||
|
validTxt.Write(proxy)
|
||||||
|
datasetWriter.Write(proxy)
|
||||||
|
|
||||||
|
slog.Info("proxy_verified", "country", proxy.Country, "latency", proxy.Latency.Milliseconds())
|
||||||
|
|
||||||
}(rawLink)
|
}(rawLink)
|
||||||
|
|
||||||
countProcessed++
|
|
||||||
if countProcessed % 1000 == 0 {
|
|
||||||
slog.Info("progress_report", "processed", countProcessed, "valid", countValid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
slog.Info("scan_finished", "total_processed", countProcessed, "total_valid", countValid)
|
slog.Info("scan_finished")
|
||||||
}
|
}
|
||||||
9
data/alive_proxies.txt
Normal file
9
data/alive_proxies.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
vless://b4bd0613-ff7c-4f2f-954d-185915e6ddad@216.239.38.120:443?path=%2F%40JavidnamanIran%2FJavid-SHAH-KingRezaPahlavi%2F&security=tls&encryption=none&insecure=0&host=o-cdn.igoii.org&type=ws&allowInsecure=0&sni=o-cdn.igoii.org#%F0%9F%86%98%EF%B8%8F%20%F0%9F%87%A9%F0%9F%87%AA%20-1
|
||||||
|
vless://33676069-bc5a-443c-bb64-14a215544f2b@deu711.deulucker.org:444?mode=auto&path=%2Fapi%2Fv1%2F&security=reality&encryption=none&pbk=BhTJ3phnq-Z-10aFKSsj1lzhA8mULR4L6leE4-0WTAs&fp=chrome&type=xhttp&sni=deu711.deulucker.org#@Vip_Security join us - 68
|
||||||
|
ss://YWVzLTI1Ni1nY206S2l4THZLendqZWtHMDBybQ@38.91.100.134:8080#@Vip_Security join us - 328
|
||||||
|
trojan://5a2c16f9@one.cf.cdn.hyli.xyz:443?path=/&security=tls&host=snippets.kkii.eu.org&type=ws&sni=snippets.kkii.eu.org#@Vip_Security join us - 47
|
||||||
|
vless://7abc75eb-b58b-4e28-af59-20f41bdf7a2a@dns.ownlink.pro:443?path=%2Frestart&security=tls&alpn=h2%2Chttp%2F1.1&encryption=none&insecure=0&host=last.ownlink.pro&fp=chrome&type=ws&allowInsecure=0&sni=last.ownlink.pro#4
|
||||||
|
vless://c4426a36-247f-4abf-bf4e-e9ea0ed01c32@ip.ali.lat:2053?path=%2F&security=tls&alpn=h2%2Chttp%2F1.1&encryption=none&insecure=0&host=temp.ali.lat&type=ws&allowInsecure=0&sni=temp.ali.lat#@Vip_Security join us - 108
|
||||||
|
vless://dd0cfef0-fda9-47ec-8a65-49d7bc004f82@cf.narton.ir:443?path=%2Fvpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl-vpnowl%3Fed%3D2560&security=tls&alpn=h2&encryption=none&insecure=0&host=www.narton.ir&fp=firefox&type=ws&allowInsecure=0&sni=www.narton.ir#@Vip_Security join us - 112
|
||||||
|
vless://c077a7aa-7ec8-4117-8ffc-9ade75a5efce@chatgpt.com:2096?path=%2F&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=cdn.sheriffbus.com&fp=chrome&type=ws&allowInsecure=0&sni=cdn.sheriffbus.com#@Vip_Security join us - 106
|
||||||
|
vless://2fb8808b-b94c-42ea-9dd2-cd77d2efcc8d@www.perplexity.ai:2096?path=%2FeyJqdW5rIjoidDZLaDRBMWhpIiwicHJvdG9jb2wiOiJ2bCIsIm1vZGUiOiJwcm94eWlwIiwicGFuZWxJUHMiOltdfQ&security=tls&alpn=http%2F1.1&encryption=none&host=digikalaa.dpdns.org&fp=chrome&type=ws&sni=DiGIkALaA.dpdns.ORG#@Vip_Security join us - 66
|
||||||
@@ -1,8 +1 @@
|
|||||||
vless://9e685fe3-e0f9-482d-939c-200a3f89b363@172.64.145.38:8443?path=%2F%3Fed%3D2560fp%3Drandom&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=vyznthvt7f5fr.zjde5.de5.net&fp=random&type=ws&allowInsecure=0&sni=vyznthvt7f5fr.zjde5.de5.net#%F0%9F%87%A9%F0%9F%87%AA%20%40vmesspv
|
vless://83f03646-fb28-44cc-9d2c-8853f6c09285@104.17.162.123:8443?path=%2F%3Fed%3D%23TELEGRAM-MARAMBASHI_MARAMBASHI_MARAMBASHI_MARAMBASHI_MARAMBASHI%3Fed%3D512&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=r4fnviw9jl4i4rx.zjde5.de5.net&fp=random&type=ws&allowInsecure=0&sni=r4fnviw9jl4i4rx.zjde5.de5.net#@Vip_Security join us - 98
|
||||||
vless://bb8c74a1-abc1-4511-b100-9876e30cb65c@172.64.145.38:8443?path=%2F%3Fed%3D2560&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=xfjd79v2tjscrm6jqo.zjde5.de5.net&fp=chrome&type=ws&allowInsecure=0&sni=xfjd79v2tjscrm6jqo.zjde5.de5.net#@Vip_Security join us - 55
|
|
||||||
vless://f85f60b1-2b96-49e9-8bde-b656d1516df0@104.17.165.123:8443?path=%2F%3Fed%3D2560&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=gx8rw8fz783ncefn332y7uyfsvb59o820mryrxu1cj19jiuuur.zjde5.de5.net&fp=chrome&type=ws&allowInsecure=0&sni=gx8rw8fz783ncefn332y7uyfsvb59o820mryrxu1cj19jiuuur.zjde5.de5.net#@Vip_Security join us - 67
|
|
||||||
vless://4525c260-df3c-4f62-b8f1-f4f5f305694b@104.17.164.123:8443?path=%2F%3Fed%3D2560&security=tls&encryption=none&insecure=0&host=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net&fp=chrome&type=ws&allowInsecure=0&sni=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net#%F0%9F%8C%8E%20%40vmesspv
|
|
||||||
vless://9e685fe3-e0f9-482d-939c-200a3f89b363@172.64.145.38:8443?path=%2F%3Fed%3D2560fp%3Drandom&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=vyznthvt7f5fr.zjde5.de5.net&fp=random&type=ws&allowInsecure=0&sni=vyznthvt7f5fr.zjde5.de5.net#%F0%9F%87%A9%F0%9F%87%AA%20%40vmesspv
|
|
||||||
vless://4525c260-df3c-4f62-b8f1-f4f5f305694b@66.81.247.155:443?path=%2F%3Fed%3D512&security=tls&encryption=none&insecure=0&host=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net&fp=chrome&type=ws&allowInsecure=0&sni=yyzsuabw9e3qd5ud7ihi5dxm96oglnsvr83cjojnm1efncfhr9ucordq.zjde5.de5.net#%F0%9F%8C%8E%20%40vmesspv
|
|
||||||
vless://83f03646-fb28-44cc-9d2c-8853f6c09285@104.17.162.123:8443?path=%2F%3Fed%3D%23TELEGRAM-Yam%3Fed%3D512&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=r4fnviw9jl4i4rx.zjde5.de5.net&fp=random&type=ws&allowInsecure=0&sni=r4fnviw9jl4i4rx.zjde5.de5.net#@chthxyz - 61
|
|
||||||
vless://3a4ddfac-e7da-48c9-9648-4a366109fc3a@api.steamsale.ir:443?path=%2FX0PX5Vup1qlVVzhxp6ic50a&security=tls&alpn=http%2F1.1&encryption=none&insecure=0&host=mtn.vpsmee.ir&fp=chrome&type=ws&allowInsecure=0&sni=cdn.vpsmee.ir#@chthxyz - 28
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ type Config struct {
|
|||||||
OutputPath string `envconfig:"OUTPUT_PATH" default:"valid.jsonl"`
|
OutputPath string `envconfig:"OUTPUT_PATH" default:"valid.jsonl"`
|
||||||
GeoIPPath string `envconfig:"GEOIP_PATH" default:"GeoLite2-Country.mmdb"`
|
GeoIPPath string `envconfig:"GEOIP_PATH" default:"GeoLite2-Country.mmdb"`
|
||||||
TxtOutputPath string `envconfig:"TXT_OUTPUT_PATH" default:"valid.txt"`
|
TxtOutputPath string `envconfig:"TXT_OUTPUT_PATH" default:"valid.txt"`
|
||||||
|
AliveOutputPath string `envconfig:"ALIVE_OUTPUT_PATH" default:"alive.jsonl"`
|
||||||
|
AliveTxtOutputPath string `envconfig:"ALIVE_TXT_OUTPUT_PATH" default:"alive.txt"`
|
||||||
|
DatasetOutputPath string `envconfig:"DATASET_OUTPUT_PATH" default:"dataset.jsonl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reads .env and processes environment variables
|
// Load reads .env and processes environment variables
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dedup
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"find-me-internet/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
@@ -16,9 +17,11 @@ func New() *Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check returns true if the item is NEW (not seen before)
|
// Seen checks if the proxy is new.
|
||||||
func (f *Filter) Seen(address string, port int) bool {
|
// Key format: "vless://1.2.3.4:443"
|
||||||
key := fmt.Sprintf("%s:%d", address, port)
|
// This allows the same IP to be scanned again if it uses a different protocol.
|
||||||
|
func (f *Filter) Seen(p *model.Proxy) bool {
|
||||||
|
key := fmt.Sprintf("%s://%s:%d", p.Type, p.Address, p.Port)
|
||||||
|
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
_, exists := f.seen[key]
|
_, exists := f.seen[key]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package filter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -18,53 +17,46 @@ func NewPipeline(timeout time.Duration) *Pipeline {
|
|||||||
return &Pipeline{Timeout: timeout}
|
return &Pipeline{Timeout: timeout}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check performs cheap checks and updates the Proxy model with results.
|
||||||
|
// Returns true ONLY if all checks pass.
|
||||||
func (f *Pipeline) Check(p *model.Proxy) bool {
|
func (f *Pipeline) Check(p *model.Proxy) bool {
|
||||||
target := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
// 1. TCP Check
|
||||||
log := slog.With("target", target, "protocol", p.Type)
|
|
||||||
|
|
||||||
// 1. TCP Connectivity
|
|
||||||
start := time.Now()
|
|
||||||
if !f.checkTCP(p) {
|
if !f.checkTCP(p) {
|
||||||
log.Debug("tcp_connect_failed", "duration", time.Since(start))
|
|
||||||
p.IsOnline = false
|
p.IsOnline = false
|
||||||
|
p.Status = "dead"
|
||||||
|
p.FailureStage = "filter"
|
||||||
|
p.FailureReason = "tcp_timeout_or_refused"
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
p.IsOnline = true
|
p.IsOnline = true
|
||||||
|
|
||||||
// 2. TLS Handshake
|
// 2. TLS Check
|
||||||
// Only proceed if protocol supports/requires TLS
|
// Determine if TLS is required
|
||||||
shouldCheckTLS := p.SNI != "" || p.Port == 443 || p.Type == model.TypeVLESS || p.Type == model.TypeTrojan
|
shouldCheckTLS := p.SNI != "" || p.Port == 443 || p.Type == model.TypeVLESS || p.Type == model.TypeTrojan
|
||||||
|
|
||||||
if shouldCheckTLS {
|
if shouldCheckTLS {
|
||||||
sni := p.SNI
|
sni := p.SNI
|
||||||
if sni == "" {
|
if sni == "" { sni = p.Address }
|
||||||
sni = p.Address // Fallback for handshake
|
|
||||||
}
|
|
||||||
|
|
||||||
startTLS := time.Now()
|
|
||||||
if !f.checkTLS(p, sni) {
|
if !f.checkTLS(p, sni) {
|
||||||
log.Debug("tls_handshake_failed",
|
|
||||||
"sni", sni,
|
|
||||||
"duration", time.Since(startTLS),
|
|
||||||
)
|
|
||||||
p.IsTLSSecure = false
|
p.IsTLSSecure = false
|
||||||
|
p.Status = "dead"
|
||||||
|
p.FailureStage = "filter"
|
||||||
|
p.FailureReason = "tls_handshake_failed"
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
p.IsTLSSecure = true
|
p.IsTLSSecure = true
|
||||||
log.Debug("network_checks_passed", "duration", time.Since(start))
|
|
||||||
} else {
|
|
||||||
log.Debug("network_checks_passed", "note", "tls_skipped_no_sni")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we got here, it passed the filter stage
|
||||||
|
p.FailureStage = "none"
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Pipeline) checkTCP(p *model.Proxy) bool {
|
func (f *Pipeline) checkTCP(p *model.Proxy) bool {
|
||||||
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
||||||
conn, err := net.DialTimeout("tcp", address, f.Timeout)
|
conn, err := net.DialTimeout("tcp", address, f.Timeout)
|
||||||
if err != nil {
|
if err != nil { return false }
|
||||||
return false
|
|
||||||
}
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -72,16 +64,9 @@ func (f *Pipeline) checkTCP(p *model.Proxy) bool {
|
|||||||
func (f *Pipeline) checkTLS(p *model.Proxy, sni string) bool {
|
func (f *Pipeline) checkTLS(p *model.Proxy, sni string) bool {
|
||||||
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
||||||
dialer := &net.Dialer{Timeout: f.Timeout}
|
dialer := &net.Dialer{Timeout: f.Timeout}
|
||||||
|
conf := &tls.Config{InsecureSkipVerify: true, ServerName: sni}
|
||||||
conf := &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
ServerName: sni,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := tls.DialWithDialer(dialer, "tcp", address, conf)
|
conn, err := tls.DialWithDialer(dialer, "tcp", address, conf)
|
||||||
if err != nil {
|
if err != nil { return false }
|
||||||
return false
|
|
||||||
}
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ProxyType defines the protocol (vless, vmess, etc.)
|
|
||||||
type ProxyType string
|
type ProxyType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -13,25 +12,25 @@ const (
|
|||||||
TypeUnknown ProxyType = "unknown"
|
TypeUnknown ProxyType = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy represents a single internet access point
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
// Identity
|
// --- Identity ---
|
||||||
RawLink string `json:"link"`
|
RawLink string `json:"link"`
|
||||||
Type ProxyType `json:"type"`
|
Type ProxyType `json:"type"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Network string `json:"network"`
|
||||||
|
SNI string `json:"sni"`
|
||||||
|
|
||||||
// Connection Details
|
// --- Enrichment ---
|
||||||
Address string `json:"address"` // IP or Domain
|
Country string `json:"country"` // e.g., "US", "IR", "DE"
|
||||||
Port int `json:"port"`
|
|
||||||
UUID string `json:"uuid"` // Or Password
|
|
||||||
SNI string `json:"sni"` // TLS Server Name Indicator
|
|
||||||
Network string `json:"network"` // tcp, ws, grpc, h2
|
|
||||||
|
|
||||||
// Filter Stage Results
|
// --- Metrics ---
|
||||||
IsOnline bool `json:"is_online"` // TCP Connect success
|
|
||||||
IsTLSSecure bool `json:"is_tls_secure"` // TLS Handshake success
|
|
||||||
|
|
||||||
// Tester Stage Results
|
|
||||||
Latency time.Duration `json:"latency_ms"`
|
Latency time.Duration `json:"latency_ms"`
|
||||||
Country string `json:"country_code"`
|
IsOnline bool `json:"is_online"` // TCP Connect Status
|
||||||
PacketLoss float64 `json:"packet_loss"` // 0.0 to 1.0
|
IsTLSSecure bool `json:"is_tls_secure"` // TLS Handshake Status
|
||||||
|
|
||||||
|
// --- Data Collection (The fields you want filled) ---
|
||||||
|
Status string `json:"status"` // "valid", "alive", "dead"
|
||||||
|
FailureStage string `json:"failure_stage"` // "filter", "tester", "none"
|
||||||
|
FailureReason string `json:"failure_reason"` // "tcp_timeout", "http_502", "tls_error", etc.
|
||||||
}
|
}
|
||||||
@@ -71,17 +71,20 @@ func (r *Runner) Test(p *model.Proxy) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. HTTP Probe
|
// 5. HTTP Probe
|
||||||
startProbe := time.Now()
|
|
||||||
latency, err := r.measureLatency(port)
|
latency, err := r.measureLatency(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("http_probe_failed",
|
// SET THE MODEL VALUES HERE
|
||||||
"duration", time.Since(startProbe),
|
p.Status = "alive" // It passed TCP, so it's "alive" but failed the test
|
||||||
"error", err,
|
p.FailureStage = "tester"
|
||||||
)
|
p.FailureReason = err.Error() // e.g., "http_timeout" or "status_502"
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
p.Latency = latency
|
p.Latency = latency
|
||||||
|
p.Status = "valid"
|
||||||
|
p.FailureStage = "none"
|
||||||
|
p.FailureReason = "none"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +98,17 @@ func (r *Runner) measureLatency(port int) (time.Duration, error) {
|
|||||||
Timeout: r.Timeout,
|
Timeout: r.Timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := client.Get(r.TestURL)
|
resp, err := client.Get(r.TestURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
// Return specific error string for the model
|
||||||
|
return 0, fmt.Errorf("http_timeout_or_network_error")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
return 0, fmt.Errorf("unexpected_status_code_%d", resp.StatusCode)
|
// Return specific status code error
|
||||||
|
return 0, fmt.Errorf("http_error_%d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Since(start), nil
|
return time.Since(start), nil
|
||||||
|
|||||||
Reference in New Issue
Block a user