90 lines
2.1 KiB
Go
90 lines
2.1 KiB
Go
package netcheck
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"vpnclient/internal/config"
|
|
"vpnclient/internal/linknorm"
|
|
"vpnclient/internal/protocols/hysteria2"
|
|
)
|
|
|
|
// Result is a TCP reachability measurement to a proxy host.
|
|
type Result struct {
|
|
Name string `json:"name"`
|
|
Host string `json:"host"`
|
|
Port string `json:"port"`
|
|
Ms int64 `json:"ms"`
|
|
OK bool `json:"ok"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// PingProxyURI measures TCP connect time to the host in a share/proxy URI.
|
|
func PingProxyURI(ctx context.Context, name, proxyURI string) Result {
|
|
return PingProfile(ctx, name, "", proxyURI)
|
|
}
|
|
|
|
// PingProfile pings using protocol hints when available.
|
|
func PingProfile(ctx context.Context, name string, proto config.Protocol, proxyURI string) Result {
|
|
res := Result{Name: name}
|
|
if strings.TrimSpace(proxyURI) == "" {
|
|
res.Error = "нет ссылки сервера"
|
|
return res
|
|
}
|
|
|
|
var host, port string
|
|
if proto == config.ProtocolHysteria2 || hysteria2.Detect(proxyURI) {
|
|
h, p, err := hysteria2.HostPort(proxyURI)
|
|
if err != nil {
|
|
res.Error = err.Error()
|
|
return res
|
|
}
|
|
host, port = h, p
|
|
} else {
|
|
normalized, _, _, err := linknorm.Normalize(proto, proxyURI)
|
|
if err != nil {
|
|
res.Error = err.Error()
|
|
return res
|
|
}
|
|
// Parse host from normalized URI without importing net/url dance twice
|
|
rest := normalized
|
|
if i := strings.Index(rest, "://"); i >= 0 {
|
|
rest = rest[i+3:]
|
|
}
|
|
if at := strings.LastIndex(rest, "@"); at >= 0 {
|
|
rest = rest[at+1:]
|
|
}
|
|
if i := strings.IndexAny(rest, "/?#"); i >= 0 {
|
|
rest = rest[:i]
|
|
}
|
|
host = rest
|
|
port = "443"
|
|
if i := strings.LastIndex(rest, ":"); i >= 0 {
|
|
host = rest[:i]
|
|
port = rest[i+1:]
|
|
}
|
|
}
|
|
|
|
res.Host = host
|
|
res.Port = port
|
|
if host == "" {
|
|
res.Error = "нет хоста"
|
|
return res
|
|
}
|
|
|
|
d := net.Dialer{Timeout: 4 * time.Second}
|
|
start := time.Now()
|
|
conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort(host, port))
|
|
if err != nil {
|
|
res.Error = fmt.Sprintf("недоступен: %v", err)
|
|
return res
|
|
}
|
|
_ = conn.Close()
|
|
res.Ms = time.Since(start).Milliseconds()
|
|
res.OK = true
|
|
return res
|
|
}
|