reality_scan.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. package service
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "fmt"
  7. "net"
  8. "slices"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "time"
  13. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  14. "github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
  15. )
  16. const (
  17. realityScanTimeout = 10 * time.Second
  18. realityDiscoverTimeout = 4 * time.Second
  19. realityScanConcurrency = 32
  20. realityDiscoverMaxIPs = 256
  21. realityScanMaxTotal = 512
  22. )
  23. var defaultRealityScanCandidates = []string{
  24. "www.cloudflare.com:443",
  25. "www.microsoft.com:443",
  26. "www.amazon.com:443",
  27. "aws.amazon.com:443",
  28. "www.samsung.com:443",
  29. "www.nvidia.com:443",
  30. "www.amd.com:443",
  31. "www.intel.com:443",
  32. "www.sony.com:443",
  33. "dl.google.com:443",
  34. }
  35. type RealityScanResult struct {
  36. Target string `json:"target" example:"www.cloudflare.com:443"`
  37. Host string `json:"host" example:"www.cloudflare.com"`
  38. IP string `json:"ip" example:"104.16.124.96"`
  39. Port int `json:"port" example:"443"`
  40. Feasible bool `json:"feasible" example:"true"`
  41. TLS13 bool `json:"tls13" example:"true"`
  42. TLSVersion string `json:"tlsVersion" example:"1.3"`
  43. H2 bool `json:"h2" example:"true"`
  44. ALPN string `json:"alpn" example:"h2"`
  45. X25519 bool `json:"x25519" example:"true"`
  46. CurveID string `json:"curveID" example:"X25519"`
  47. CertValid bool `json:"certValid" example:"true"`
  48. CertSubject string `json:"certSubject" example:"cloudflare.com"`
  49. CertIssuer string `json:"certIssuer" example:"Google Trust Services"`
  50. NotAfter string `json:"notAfter" example:"2026-08-01T00:00:00Z"`
  51. ServerNames []string `json:"serverNames"`
  52. LatencyMs int `json:"latencyMs" example:"180"`
  53. Reason string `json:"reason" example:""`
  54. }
  55. type realityProbeTask struct {
  56. dialHost string
  57. port int
  58. sni string
  59. timeout time.Duration
  60. bulk bool
  61. }
  62. func tlsVersionName(v uint16) string {
  63. switch v {
  64. case tls.VersionTLS13:
  65. return "1.3"
  66. case tls.VersionTLS12:
  67. return "1.2"
  68. case tls.VersionTLS11:
  69. return "1.1"
  70. case tls.VersionTLS10:
  71. return "1.0"
  72. default:
  73. return "unknown"
  74. }
  75. }
  76. func realityCurveName(id tls.CurveID) string {
  77. switch id {
  78. case tls.X25519:
  79. return "X25519"
  80. case tls.X25519MLKEM768:
  81. return "X25519MLKEM768"
  82. case tls.CurveP256:
  83. return "P-256"
  84. case tls.CurveP384:
  85. return "P-384"
  86. case tls.CurveP521:
  87. return "P-521"
  88. case 0:
  89. return ""
  90. default:
  91. return fmt.Sprintf("0x%04x", uint16(id))
  92. }
  93. }
  94. func filterUsableSANs(dnsNames []string) []string {
  95. out := make([]string, 0, len(dnsNames))
  96. for _, n := range dnsNames {
  97. n = strings.TrimSpace(n)
  98. if n == "" || strings.HasPrefix(n, "*.") {
  99. continue
  100. }
  101. out = append(out, n)
  102. }
  103. return out
  104. }
  105. func firstUsableName(leaf *x509.Certificate) string {
  106. cn := strings.TrimSpace(leaf.Subject.CommonName)
  107. if cn != "" && !strings.HasPrefix(cn, "*.") {
  108. return cn
  109. }
  110. for _, n := range leaf.DNSNames {
  111. n = strings.TrimSpace(n)
  112. if n != "" && !strings.HasPrefix(n, "*.") {
  113. return n
  114. }
  115. }
  116. return ""
  117. }
  118. func splitRealityTarget(target string) (string, int, error) {
  119. target = strings.TrimSpace(target)
  120. if target == "" {
  121. return "", 0, common.NewError("target is required")
  122. }
  123. host, portStr := target, "443"
  124. if h, p, err := net.SplitHostPort(target); err == nil {
  125. host, portStr = h, p
  126. }
  127. host, err := netsafe.NormalizeHost(host)
  128. if err != nil {
  129. return "", 0, common.NewError("invalid target host: ", err)
  130. }
  131. port, err := strconv.Atoi(portStr)
  132. if err != nil || port < 1 || port > 65535 {
  133. return "", 0, common.NewError("invalid target port")
  134. }
  135. return host, port, nil
  136. }
  137. func incIP(ip net.IP) {
  138. for j := len(ip) - 1; j >= 0; j-- {
  139. ip[j]++
  140. if ip[j] > 0 {
  141. break
  142. }
  143. }
  144. }
  145. func enumerateCIDR(cidr string, max int) ([]string, error) {
  146. _, ipnet, err := net.ParseCIDR(strings.TrimSpace(cidr))
  147. if err != nil {
  148. return nil, err
  149. }
  150. ips := make([]string, 0, max)
  151. for ip := ipnet.IP.Mask(ipnet.Mask); ipnet.Contains(ip); incIP(ip) {
  152. ips = append(ips, ip.String())
  153. if len(ips) >= max {
  154. break
  155. }
  156. }
  157. return ips, nil
  158. }
  159. func (s *ServerService) probeRealityAddr(dialHost string, port int, sni string, timeout time.Duration) *RealityScanResult {
  160. addr := net.JoinHostPort(dialHost, strconv.Itoa(port))
  161. res := &RealityScanResult{Port: port}
  162. if net.ParseIP(dialHost) != nil {
  163. res.IP = dialHost
  164. }
  165. if sni != "" {
  166. res.Host = sni
  167. res.Target = net.JoinHostPort(sni, strconv.Itoa(port))
  168. } else {
  169. res.Host = dialHost
  170. res.Target = addr
  171. }
  172. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  173. defer cancel()
  174. start := time.Now()
  175. conn, err := netsafe.SSRFGuardedDialContext(ctx, "tcp", addr)
  176. if err != nil {
  177. res.Reason = "connection failed: " + err.Error()
  178. return res
  179. }
  180. defer conn.Close()
  181. _ = conn.SetDeadline(time.Now().Add(timeout))
  182. cfg := &tls.Config{
  183. ServerName: sni,
  184. InsecureSkipVerify: true,
  185. NextProtos: []string{"h2", "http/1.1"},
  186. CurvePreferences: []tls.CurveID{tls.X25519, tls.X25519MLKEM768},
  187. MinVersion: tls.VersionTLS12,
  188. }
  189. tlsConn := tls.Client(conn, cfg)
  190. if err := tlsConn.HandshakeContext(ctx); err != nil {
  191. res.Reason = "TLS handshake failed: " + err.Error()
  192. return res
  193. }
  194. res.LatencyMs = int(time.Since(start).Milliseconds())
  195. st := tlsConn.ConnectionState()
  196. res.TLS13 = st.Version == tls.VersionTLS13
  197. res.TLSVersion = tlsVersionName(st.Version)
  198. res.ALPN = st.NegotiatedProtocol
  199. res.H2 = st.NegotiatedProtocol == "h2"
  200. res.CurveID = realityCurveName(st.CurveID)
  201. res.X25519 = st.CurveID == tls.X25519 || st.CurveID == tls.X25519MLKEM768
  202. verifyHost := sni
  203. if len(st.PeerCertificates) > 0 {
  204. leaf := st.PeerCertificates[0]
  205. res.CertSubject = leaf.Subject.CommonName
  206. if res.CertSubject == "" && len(leaf.DNSNames) > 0 {
  207. res.CertSubject = leaf.DNSNames[0]
  208. }
  209. if len(leaf.Issuer.Organization) > 0 {
  210. res.CertIssuer = leaf.Issuer.Organization[0]
  211. } else {
  212. res.CertIssuer = leaf.Issuer.CommonName
  213. }
  214. res.NotAfter = leaf.NotAfter.UTC().Format(time.RFC3339)
  215. res.ServerNames = filterUsableSANs(leaf.DNSNames)
  216. if sni == "" {
  217. if discovered := firstUsableName(leaf); discovered != "" {
  218. res.Host = discovered
  219. res.Target = net.JoinHostPort(discovered, strconv.Itoa(port))
  220. verifyHost = discovered
  221. }
  222. }
  223. if verifyHost != "" {
  224. opts := x509.VerifyOptions{DNSName: verifyHost, Intermediates: x509.NewCertPool()}
  225. for _, c := range st.PeerCertificates[1:] {
  226. opts.Intermediates.AddCert(c)
  227. }
  228. if _, verr := leaf.Verify(opts); verr == nil {
  229. res.CertValid = true
  230. } else {
  231. res.Reason = "certificate not trusted: " + verr.Error()
  232. }
  233. } else {
  234. res.Reason = "no usable domain in certificate"
  235. }
  236. } else {
  237. res.Reason = "no certificate presented"
  238. }
  239. res.Feasible = res.TLS13 && res.H2 && res.X25519 && res.CertValid
  240. if !res.Feasible && res.Reason == "" {
  241. switch {
  242. case !res.TLS13:
  243. res.Reason = "server does not negotiate TLS 1.3"
  244. case !res.H2:
  245. res.Reason = "server does not negotiate HTTP/2 (h2)"
  246. case !res.X25519:
  247. res.Reason = "server did not use X25519 key exchange"
  248. }
  249. }
  250. return res
  251. }
  252. func (s *ServerService) probeRealityTarget(host string, port int) *RealityScanResult {
  253. return s.probeRealityAddr(host, port, host, realityScanTimeout)
  254. }
  255. func (s *ServerService) ScanRealityTarget(target string) (*RealityScanResult, error) {
  256. host, port, err := splitRealityTarget(target)
  257. if err != nil {
  258. return nil, err
  259. }
  260. return s.probeRealityTarget(host, port), nil
  261. }
  262. func (s *ServerService) ScanRealityTargets(targetsCSV string) ([]*RealityScanResult, error) {
  263. var tokens []string
  264. for _, raw := range strings.Split(targetsCSV, ",") {
  265. if t := strings.TrimSpace(raw); t != "" {
  266. tokens = append(tokens, t)
  267. }
  268. }
  269. if len(tokens) == 0 {
  270. tokens = append(tokens, defaultRealityScanCandidates...)
  271. }
  272. var tasks []realityProbeTask
  273. var invalid []*RealityScanResult
  274. for _, token := range tokens {
  275. if len(tasks) >= realityScanMaxTotal {
  276. break
  277. }
  278. if strings.Contains(token, "/") {
  279. ips, err := enumerateCIDR(token, realityDiscoverMaxIPs)
  280. if err != nil {
  281. invalid = append(invalid, &RealityScanResult{Target: token, Reason: "invalid CIDR: " + err.Error()})
  282. continue
  283. }
  284. for _, ip := range ips {
  285. if len(tasks) >= realityScanMaxTotal {
  286. break
  287. }
  288. tasks = append(tasks, realityProbeTask{dialHost: ip, port: 443, timeout: realityDiscoverTimeout, bulk: true})
  289. }
  290. continue
  291. }
  292. host, port, err := splitRealityTarget(token)
  293. if err != nil {
  294. invalid = append(invalid, &RealityScanResult{Target: token, Reason: err.Error()})
  295. continue
  296. }
  297. if net.ParseIP(host) != nil {
  298. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, timeout: realityDiscoverTimeout})
  299. } else {
  300. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, sni: host, timeout: realityScanTimeout})
  301. }
  302. }
  303. probed := make([]*RealityScanResult, len(tasks))
  304. sem := make(chan struct{}, realityScanConcurrency)
  305. var wg sync.WaitGroup
  306. for i, task := range tasks {
  307. wg.Add(1)
  308. sem <- struct{}{}
  309. go func(idx int, tk realityProbeTask) {
  310. defer wg.Done()
  311. defer func() { <-sem }()
  312. r := s.probeRealityAddr(tk.dialHost, tk.port, tk.sni, tk.timeout)
  313. if tk.bulk && r.TLSVersion == "" {
  314. return
  315. }
  316. probed[idx] = r
  317. }(i, task)
  318. }
  319. wg.Wait()
  320. results := dedupRealityResults(append(probed, invalid...))
  321. sortRealityResults(results)
  322. return results, nil
  323. }
  324. func dedupRealityResults(results []*RealityScanResult) []*RealityScanResult {
  325. best := make(map[string]*RealityScanResult)
  326. order := make([]string, 0, len(results))
  327. for _, r := range results {
  328. if r == nil {
  329. continue
  330. }
  331. if ex, ok := best[r.Target]; !ok {
  332. best[r.Target] = r
  333. order = append(order, r.Target)
  334. } else if betterRealityResult(r, ex) {
  335. best[r.Target] = r
  336. }
  337. }
  338. out := make([]*RealityScanResult, 0, len(order))
  339. for _, k := range order {
  340. out = append(out, best[k])
  341. }
  342. return out
  343. }
  344. func betterRealityResult(a, b *RealityScanResult) bool {
  345. if a.Feasible != b.Feasible {
  346. return a.Feasible
  347. }
  348. return a.LatencyMs > 0 && (b.LatencyMs == 0 || a.LatencyMs < b.LatencyMs)
  349. }
  350. func sortRealityResults(results []*RealityScanResult) {
  351. slices.SortStableFunc(results, func(a, b *RealityScanResult) int {
  352. if a.Feasible != b.Feasible {
  353. if a.Feasible {
  354. return -1
  355. }
  356. return 1
  357. }
  358. return a.LatencyMs - b.LatencyMs
  359. })
  360. }