reality_scan.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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, xver int) *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. // A REALITY inbound with xver>=1 fronts a target that speaks the PROXY
  183. // protocol (e.g. an Nginx listener with `proxy_protocol`), so the probe
  184. // must lead with a PROXY header or the target resets the connection and
  185. // the scan reports a spurious handshake failure (#6082).
  186. if xver >= 1 {
  187. if err := writeProxyProtocolHeader(conn, xver); err != nil {
  188. res.Reason = "proxy protocol write failed: " + err.Error()
  189. return res
  190. }
  191. }
  192. cfg := &tls.Config{
  193. ServerName: sni,
  194. InsecureSkipVerify: true,
  195. NextProtos: []string{"h2", "http/1.1"},
  196. CurvePreferences: []tls.CurveID{tls.X25519, tls.X25519MLKEM768},
  197. MinVersion: tls.VersionTLS12,
  198. }
  199. tlsConn := tls.Client(conn, cfg)
  200. if err := tlsConn.HandshakeContext(ctx); err != nil {
  201. res.Reason = "TLS handshake failed: " + err.Error()
  202. return res
  203. }
  204. res.LatencyMs = int(time.Since(start).Milliseconds())
  205. st := tlsConn.ConnectionState()
  206. res.TLS13 = st.Version == tls.VersionTLS13
  207. res.TLSVersion = tlsVersionName(st.Version)
  208. res.ALPN = st.NegotiatedProtocol
  209. res.H2 = st.NegotiatedProtocol == "h2"
  210. res.CurveID = realityCurveName(st.CurveID)
  211. res.X25519 = st.CurveID == tls.X25519 || st.CurveID == tls.X25519MLKEM768
  212. verifyHost := sni
  213. if len(st.PeerCertificates) > 0 {
  214. leaf := st.PeerCertificates[0]
  215. res.CertSubject = leaf.Subject.CommonName
  216. if res.CertSubject == "" && len(leaf.DNSNames) > 0 {
  217. res.CertSubject = leaf.DNSNames[0]
  218. }
  219. if len(leaf.Issuer.Organization) > 0 {
  220. res.CertIssuer = leaf.Issuer.Organization[0]
  221. } else {
  222. res.CertIssuer = leaf.Issuer.CommonName
  223. }
  224. res.NotAfter = leaf.NotAfter.UTC().Format(time.RFC3339)
  225. res.ServerNames = filterUsableSANs(leaf.DNSNames)
  226. if sni == "" {
  227. if discovered := firstUsableName(leaf); discovered != "" {
  228. res.Host = discovered
  229. res.Target = net.JoinHostPort(discovered, strconv.Itoa(port))
  230. verifyHost = discovered
  231. }
  232. }
  233. if verifyHost != "" {
  234. opts := x509.VerifyOptions{DNSName: verifyHost, Intermediates: x509.NewCertPool()}
  235. for _, c := range st.PeerCertificates[1:] {
  236. opts.Intermediates.AddCert(c)
  237. }
  238. if _, verr := leaf.Verify(opts); verr == nil {
  239. res.CertValid = true
  240. } else {
  241. res.Reason = "certificate not trusted: " + verr.Error()
  242. }
  243. } else {
  244. res.Reason = "no usable domain in certificate"
  245. }
  246. } else {
  247. res.Reason = "no certificate presented"
  248. }
  249. res.Feasible = res.TLS13 && res.H2 && res.X25519 && res.CertValid
  250. if !res.Feasible && res.Reason == "" {
  251. switch {
  252. case !res.TLS13:
  253. res.Reason = "server does not negotiate TLS 1.3"
  254. case !res.H2:
  255. res.Reason = "server does not negotiate HTTP/2 (h2)"
  256. case !res.X25519:
  257. res.Reason = "server did not use X25519 key exchange"
  258. }
  259. }
  260. return res
  261. }
  262. func (s *ServerService) probeRealityTarget(host string, port int, xver int) *RealityScanResult {
  263. return s.probeRealityAddr(host, port, host, realityScanTimeout, xver)
  264. }
  265. func (s *ServerService) ScanRealityTarget(target string, xver int) (*RealityScanResult, error) {
  266. host, port, err := splitRealityTarget(target)
  267. if err != nil {
  268. return nil, err
  269. }
  270. return s.probeRealityTarget(host, port, xver), nil
  271. }
  272. func (s *ServerService) ScanRealityTargets(targetsCSV string) ([]*RealityScanResult, error) {
  273. var tokens []string
  274. for raw := range strings.SplitSeq(targetsCSV, ",") {
  275. if t := strings.TrimSpace(raw); t != "" {
  276. tokens = append(tokens, t)
  277. }
  278. }
  279. if len(tokens) == 0 {
  280. tokens = append(tokens, defaultRealityScanCandidates...)
  281. }
  282. var tasks []realityProbeTask
  283. var invalid []*RealityScanResult
  284. for _, token := range tokens {
  285. if len(tasks) >= realityScanMaxTotal {
  286. break
  287. }
  288. if strings.Contains(token, "/") {
  289. ips, err := enumerateCIDR(token, realityDiscoverMaxIPs)
  290. if err != nil {
  291. invalid = append(invalid, &RealityScanResult{Target: token, Reason: "invalid CIDR: " + err.Error()})
  292. continue
  293. }
  294. for _, ip := range ips {
  295. if len(tasks) >= realityScanMaxTotal {
  296. break
  297. }
  298. tasks = append(tasks, realityProbeTask{dialHost: ip, port: 443, timeout: realityDiscoverTimeout, bulk: true})
  299. }
  300. continue
  301. }
  302. host, port, err := splitRealityTarget(token)
  303. if err != nil {
  304. invalid = append(invalid, &RealityScanResult{Target: token, Reason: err.Error()})
  305. continue
  306. }
  307. if net.ParseIP(host) != nil {
  308. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, timeout: realityDiscoverTimeout})
  309. } else {
  310. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, sni: host, timeout: realityScanTimeout})
  311. }
  312. }
  313. probed := make([]*RealityScanResult, len(tasks))
  314. sem := make(chan struct{}, realityScanConcurrency)
  315. var wg sync.WaitGroup
  316. for i, task := range tasks {
  317. wg.Add(1)
  318. sem <- struct{}{}
  319. go func(idx int, tk realityProbeTask) {
  320. defer wg.Done()
  321. defer func() { <-sem }()
  322. r := s.probeRealityAddr(tk.dialHost, tk.port, tk.sni, tk.timeout, 0)
  323. if tk.bulk && r.TLSVersion == "" {
  324. return
  325. }
  326. probed[idx] = r
  327. }(i, task)
  328. }
  329. wg.Wait()
  330. results := dedupRealityResults(append(probed, invalid...))
  331. sortRealityResults(results)
  332. return results, nil
  333. }
  334. func dedupRealityResults(results []*RealityScanResult) []*RealityScanResult {
  335. best := make(map[string]*RealityScanResult)
  336. order := make([]string, 0, len(results))
  337. for _, r := range results {
  338. if r == nil {
  339. continue
  340. }
  341. if ex, ok := best[r.Target]; !ok {
  342. best[r.Target] = r
  343. order = append(order, r.Target)
  344. } else if betterRealityResult(r, ex) {
  345. best[r.Target] = r
  346. }
  347. }
  348. out := make([]*RealityScanResult, 0, len(order))
  349. for _, k := range order {
  350. out = append(out, best[k])
  351. }
  352. return out
  353. }
  354. func betterRealityResult(a, b *RealityScanResult) bool {
  355. if a.Feasible != b.Feasible {
  356. return a.Feasible
  357. }
  358. return a.LatencyMs > 0 && (b.LatencyMs == 0 || a.LatencyMs < b.LatencyMs)
  359. }
  360. func sortRealityResults(results []*RealityScanResult) {
  361. slices.SortStableFunc(results, func(a, b *RealityScanResult) int {
  362. if a.Feasible != b.Feasible {
  363. if a.Feasible {
  364. return -1
  365. }
  366. return 1
  367. }
  368. return a.LatencyMs - b.LatencyMs
  369. })
  370. }
  371. // writeProxyProtocolHeader emits a PROXY protocol header describing the local
  372. // connection so a target that requires it (Nginx `proxy_protocol`, matching a
  373. // REALITY inbound's xver) accepts the probe instead of resetting it. xver 1
  374. // sends the human-readable v1 header; xver 2 sends the binary v2 header. The
  375. // addresses come from the already-dialed connection, so they are always a
  376. // consistent, real (src, dst) pair.
  377. func writeProxyProtocolHeader(conn net.Conn, xver int) error {
  378. local, lok := conn.LocalAddr().(*net.TCPAddr)
  379. remote, rok := conn.RemoteAddr().(*net.TCPAddr)
  380. if !lok || !rok {
  381. return fmt.Errorf("connection has no TCP addresses")
  382. }
  383. if xver >= 2 {
  384. return writeProxyProtocolV2(conn, local, remote)
  385. }
  386. return writeProxyProtocolV1(conn, local, remote)
  387. }
  388. func writeProxyProtocolV1(conn net.Conn, local, remote *net.TCPAddr) error {
  389. fam := "TCP4"
  390. if local.IP.To4() == nil || remote.IP.To4() == nil {
  391. fam = "TCP6"
  392. }
  393. header := fmt.Sprintf("PROXY %s %s %s %d %d\r\n", fam, local.IP.String(), remote.IP.String(), local.Port, remote.Port)
  394. _, err := conn.Write([]byte(header))
  395. return err
  396. }
  397. func writeProxyProtocolV2(conn net.Conn, local, remote *net.TCPAddr) error {
  398. buf := []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}
  399. buf = append(buf, 0x21)
  400. src4, dst4 := local.IP.To4(), remote.IP.To4()
  401. if src4 != nil && dst4 != nil {
  402. buf = append(buf, 0x11)
  403. buf = append(buf, 0x00, 12)
  404. buf = append(buf, src4...)
  405. buf = append(buf, dst4...)
  406. buf = append(buf, byte(local.Port>>8), byte(local.Port))
  407. buf = append(buf, byte(remote.Port>>8), byte(remote.Port))
  408. } else {
  409. buf = append(buf, 0x21)
  410. buf = append(buf, 0x00, 36)
  411. buf = append(buf, local.IP.To16()...)
  412. buf = append(buf, remote.IP.To16()...)
  413. buf = append(buf, byte(local.Port>>8), byte(local.Port))
  414. buf = append(buf, byte(remote.Port>>8), byte(remote.Port))
  415. }
  416. _, err := conn.Write(buf)
  417. return err
  418. }