// Package reality implements TLS scanning to find servers suitable for REALITY protocol.
package reality
import (
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"time"
)
// Host represents a target to scan.
type Host struct {
IP net.IP
Origin string // original input (domain, IP, or CIDR string)
}
// ScanResult holds the outcome of a TLS scan.
type ScanResult struct {
IP string
Origin string
CertDomain string // certificate Subject.CommonName
CertIssuer string // pipe-delimited Issuer.Organization
GeoCode string // ISO country code, filled in by caller
TLSVersion uint16
ALPN string
Feasible bool
Error error
}
// ScanConfig controls scanning behavior.
type ScanConfig struct {
Port int
Timeout time.Duration
}
// ScanHost performs a TLS handshake against the given host and checks REALITY feasibility.
func ScanHost(ctx context.Context, host Host, cfg ScanConfig) ScanResult {
result := ScanResult{
IP: host.IP.String(),
Origin: host.Origin,
}
addr := fmt.Sprintf("%s:%d", host.IP.String(), cfg.Port)
tlsCfg := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2", "http/1.1"},
CurvePreferences: []tls.CurveID{tls.X25519},
}
// Set SNI if the origin is a domain name.
if net.ParseIP(host.Origin) == nil {
tlsCfg.ServerName = host.Origin
}
dialer := &net.Dialer{Timeout: cfg.Timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
result.Error = fmt.Errorf("dial: %w", err)
return result
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(cfg.Timeout))
tlsConn := tls.Client(conn, tlsCfg)
if err := tlsConn.HandshakeContext(ctx); err != nil {
result.Error = fmt.Errorf("tls handshake: %w", err)
return result
}
defer tlsConn.Close()
state := tlsConn.ConnectionState()
result.TLSVersion = state.Version
result.ALPN = state.NegotiatedProtocol
if len(state.PeerCertificates) > 0 {
cert := state.PeerCertificates[0]
result.CertDomain = cert.Subject.CommonName
if len(cert.Issuer.Organization) > 0 {
result.CertIssuer = strings.Join(cert.Issuer.Organization, " | ")
}
}
result.Feasible = result.TLSVersion == tls.VersionTLS13 &&
result.ALPN == "h2" &&
result.CertDomain != "" &&
result.CertIssuer != ""
return result
}
// TLSVersionName returns a human-readable name for a TLS version.
func TLSVersionName(v uint16) string {
switch v {
case tls.VersionTLS10:
return "TLS 1.0"
case tls.VersionTLS11:
return "TLS 1.1"
case tls.VersionTLS12:
return "TLS 1.2"
case tls.VersionTLS13:
return "TLS 1.3"
default:
return fmt.Sprintf("0x%04x", v)
}
}