mirror of
https://github.com/SajadMRjl/find-me-internet.git
synced 2026-07-13 05:46:27 +00:00
chore: init project
This commit is contained in:
84
internal/filter/filter.go
Normal file
84
internal/filter/filter.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"find-me-internet/internal/model"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Pipeline represents the filter configuration
|
||||
type Pipeline struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func NewPipeline(timeout time.Duration) *Pipeline {
|
||||
return &Pipeline{Timeout: timeout}
|
||||
}
|
||||
|
||||
// Check performs the TCP & TLS checks
|
||||
// Returns true if the proxy is worth testing with Sing-box
|
||||
func (f *Pipeline) Check(p *model.Proxy) bool {
|
||||
// 1. Syntax Check
|
||||
if p.Address == "" || p.Port == 0 || p.Type == model.TypeUnknown {
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. TCP Connect (Liveness)
|
||||
if !f.checkTCP(p) {
|
||||
p.IsOnline = false
|
||||
return false // Dead
|
||||
}
|
||||
p.IsOnline = true
|
||||
|
||||
// 3. TLS Handshake (Validity)
|
||||
// Only run this if the proxy uses TLS (SNI is present or it's a TLS protocol)
|
||||
// For VLESS/Trojan, TLS is standard. For VMess, it's optional.
|
||||
if p.SNI != "" || p.Port == 443 {
|
||||
if !f.checkTLS(p) {
|
||||
p.IsTLSSecure = false
|
||||
// If it expects TLS but fails handshake, it's likely a firewall block or dead cert
|
||||
return false
|
||||
}
|
||||
p.IsTLSSecure = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// checkTCP attempts a raw socket connection
|
||||
func (f *Pipeline) checkTCP(p *model.Proxy) bool {
|
||||
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
||||
conn, err := net.DialTimeout("tcp", address, f.Timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// checkTLS attempts a TLS handshake
|
||||
func (f *Pipeline) checkTLS(p *model.Proxy) bool {
|
||||
address := net.JoinHostPort(p.Address, strconv.Itoa(p.Port))
|
||||
|
||||
dialer := &net.Dialer{Timeout: f.Timeout}
|
||||
|
||||
// We use InsecureSkipVerify because many proxies use self-signed certs or Reality.
|
||||
// We only care that the server *speaks* TLS and accepts our SNI.
|
||||
conf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: p.SNI,
|
||||
}
|
||||
|
||||
// If no SNI is parsed, try the host address (common for direct connections)
|
||||
if conf.ServerName == "" {
|
||||
conf.ServerName = p.Address
|
||||
}
|
||||
|
||||
conn, err := tls.DialWithDialer(dialer, "tcp", address, conf)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
37
internal/model/proxy.go
Normal file
37
internal/model/proxy.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// ProxyType defines the protocol (vless, vmess, etc.)
|
||||
type ProxyType string
|
||||
|
||||
const (
|
||||
TypeVLESS ProxyType = "vless"
|
||||
TypeVMess ProxyType = "vmess"
|
||||
TypeTrojan ProxyType = "trojan"
|
||||
TypeShadowsocks ProxyType = "ss"
|
||||
TypeUnknown ProxyType = "unknown"
|
||||
)
|
||||
|
||||
// Proxy represents a single internet access point
|
||||
type Proxy struct {
|
||||
// Identity
|
||||
RawLink string `json:"link"`
|
||||
Type ProxyType `json:"type"`
|
||||
|
||||
// Connection Details
|
||||
Address string `json:"address"` // IP or Domain
|
||||
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
|
||||
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"`
|
||||
Country string `json:"country_code"`
|
||||
PacketLoss float64 `json:"packet_loss"` // 0.0 to 1.0
|
||||
}
|
||||
58
internal/parser/parser.go
Normal file
58
internal/parser/parser.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"find-me-internet/internal/model"
|
||||
|
||||
"github.com/gvcgo/vpnparser/pkgs/outbound"
|
||||
)
|
||||
|
||||
// ParseLink converts a raw proxy string (vless://, vmess://) into our internal Model
|
||||
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)
|
||||
}
|
||||
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".
|
||||
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) {
|
||||
case "vless":
|
||||
p.Type = model.TypeVLESS
|
||||
if strings.Contains(raw, "reality") {
|
||||
p.Type = model.TypeVLESS // We treat Reality as VLESS with special TLS options
|
||||
}
|
||||
case "vmess":
|
||||
p.Type = model.TypeVMess
|
||||
case "trojan":
|
||||
p.Type = model.TypeTrojan
|
||||
case "shadowsocks":
|
||||
p.Type = model.TypeShadowsocks
|
||||
default:
|
||||
p.Type = model.TypeUnknown
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
Reference in New Issue
Block a user