1
0

reality_scan.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. package service
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "slices"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "time"
  14. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  15. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  16. "github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
  17. )
  18. const (
  19. realityScanTimeout = 10 * time.Second
  20. realityDiscoverTimeout = 4 * time.Second
  21. realityScanConcurrency = 32
  22. realityDiscoverMaxIPs = 256
  23. realityScanMaxTotal = 512
  24. )
  25. var defaultRealityScanCandidates = []string{
  26. "www.cloudflare.com:443",
  27. "www.microsoft.com:443",
  28. "www.amazon.com:443",
  29. "aws.amazon.com:443",
  30. "www.samsung.com:443",
  31. "www.nvidia.com:443",
  32. "www.amd.com:443",
  33. "www.intel.com:443",
  34. "www.sony.com:443",
  35. "dl.google.com:443",
  36. }
  37. // DefaultRealityScanCandidatesCSV is the shipped default for the
  38. // realityScanCandidates setting (comma-separated host:port list).
  39. var DefaultRealityScanCandidatesCSV = strings.Join(defaultRealityScanCandidates, ",")
  40. type RealityScanResult struct {
  41. Target string `json:"target" example:"www.cloudflare.com:443"`
  42. Host string `json:"host" example:"www.cloudflare.com"`
  43. IP string `json:"ip" example:"104.16.124.96"`
  44. Port int `json:"port" example:"443"`
  45. Feasible bool `json:"feasible" example:"true"`
  46. // PrivateTarget marks a target that resolves to a loopback/private/link-local
  47. // address: blocked before the probe unless the caller opted in, then flagged.
  48. PrivateTarget bool `json:"privateTarget" example:"false"`
  49. TLS13 bool `json:"tls13" example:"true"`
  50. TLSVersion string `json:"tlsVersion" example:"1.3"`
  51. H2 bool `json:"h2" example:"true"`
  52. ALPN string `json:"alpn" example:"h2"`
  53. X25519 bool `json:"x25519" example:"true"`
  54. CurveID string `json:"curveID" example:"X25519"`
  55. CertValid bool `json:"certValid" example:"true"`
  56. // CertChainValid ignores the name: a trusted chain presented for other names
  57. // still has serverNames the panel can offer instead of the failing SNI.
  58. CertChainValid bool `json:"certChainValid" example:"true"`
  59. // CertChainBytes is the sum of DER lengths of the presented peer chain.
  60. // xray-core ML-DSA-65 REALITY needs >= 3500 bytes (constant lives in xray-core).
  61. CertChainBytes int `json:"certChainBytes" example:"3427"`
  62. CertSubject string `json:"certSubject" example:"cloudflare.com"`
  63. CertIssuer string `json:"certIssuer" example:"Google Trust Services"`
  64. NotAfter string `json:"notAfter" example:"2026-08-01T00:00:00Z"`
  65. ServerNames []string `json:"serverNames"`
  66. LatencyMs int `json:"latencyMs" example:"180"`
  67. Reason string `json:"reason" example:""`
  68. }
  69. type realityProbeTask struct {
  70. dialHost string
  71. port int
  72. sni string
  73. timeout time.Duration
  74. bulk bool
  75. }
  76. func tlsVersionName(v uint16) string {
  77. switch v {
  78. case tls.VersionTLS13:
  79. return "1.3"
  80. case tls.VersionTLS12:
  81. return "1.2"
  82. case tls.VersionTLS11:
  83. return "1.1"
  84. case tls.VersionTLS10:
  85. return "1.0"
  86. default:
  87. return "unknown"
  88. }
  89. }
  90. func realityCurveName(id tls.CurveID) string {
  91. switch id {
  92. case tls.X25519:
  93. return "X25519"
  94. case tls.X25519MLKEM768:
  95. return "X25519MLKEM768"
  96. case tls.CurveP256:
  97. return "P-256"
  98. case tls.CurveP384:
  99. return "P-384"
  100. case tls.CurveP521:
  101. return "P-521"
  102. case 0:
  103. return ""
  104. default:
  105. return fmt.Sprintf("0x%04x", uint16(id))
  106. }
  107. }
  108. func filterUsableSANs(dnsNames []string) []string {
  109. out := make([]string, 0, len(dnsNames))
  110. for _, n := range dnsNames {
  111. n = strings.TrimSpace(n)
  112. if n == "" || strings.HasPrefix(n, "*.") {
  113. continue
  114. }
  115. out = append(out, n)
  116. }
  117. return out
  118. }
  119. func firstUsableName(leaf *x509.Certificate) string {
  120. cn := strings.TrimSpace(leaf.Subject.CommonName)
  121. if cn != "" && !strings.HasPrefix(cn, "*.") {
  122. return cn
  123. }
  124. for _, n := range leaf.DNSNames {
  125. n = strings.TrimSpace(n)
  126. if n != "" && !strings.HasPrefix(n, "*.") {
  127. return n
  128. }
  129. }
  130. return ""
  131. }
  132. func leafVerifies(leaf *x509.Certificate, opts x509.VerifyOptions) bool {
  133. _, err := leaf.Verify(opts)
  134. return err == nil
  135. }
  136. func splitRealityTarget(target string) (string, int, error) {
  137. target = strings.TrimSpace(target)
  138. if target == "" {
  139. return "", 0, common.NewError("target is required")
  140. }
  141. host, portStr := target, "443"
  142. if h, p, err := net.SplitHostPort(target); err == nil {
  143. host, portStr = h, p
  144. }
  145. host, err := netsafe.NormalizeHost(host)
  146. if err != nil {
  147. return "", 0, common.NewError("invalid target host: ", err)
  148. }
  149. port, err := strconv.Atoi(portStr)
  150. if err != nil || port < 1 || port > 65535 {
  151. return "", 0, common.NewError("invalid target port")
  152. }
  153. return host, port, nil
  154. }
  155. func incIP(ip net.IP) {
  156. for j := len(ip) - 1; j >= 0; j-- {
  157. ip[j]++
  158. if ip[j] > 0 {
  159. break
  160. }
  161. }
  162. }
  163. func enumerateCIDR(cidr string, max int) ([]string, error) {
  164. _, ipnet, err := net.ParseCIDR(strings.TrimSpace(cidr))
  165. if err != nil {
  166. return nil, err
  167. }
  168. ips := make([]string, 0, max)
  169. for ip := ipnet.IP.Mask(ipnet.Mask); ipnet.Contains(ip); incIP(ip) {
  170. ips = append(ips, ip.String())
  171. if len(ips) >= max {
  172. break
  173. }
  174. }
  175. return ips, nil
  176. }
  177. func (s *ServerService) probeRealityAddr(dialHost string, port int, sni string, timeout time.Duration, xver int, allowPrivate bool) *RealityScanResult {
  178. addr := net.JoinHostPort(dialHost, strconv.Itoa(port))
  179. res := &RealityScanResult{Port: port}
  180. if net.ParseIP(dialHost) != nil {
  181. res.IP = dialHost
  182. }
  183. // Target stays the dialed address (it is what the inbound dials); Host is
  184. // the SNI the handshake sent, which may differ for a fronting proxy.
  185. res.Host = dialHost
  186. res.Target = addr
  187. if sni != "" {
  188. res.Host = sni
  189. }
  190. ctx, cancel := context.WithTimeout(netsafe.ContextWithAllowPrivate(context.Background(), allowPrivate), timeout)
  191. defer cancel()
  192. start := time.Now()
  193. conn, err := netsafe.SSRFGuardedDialContext(ctx, "tcp", addr)
  194. if err != nil {
  195. res.PrivateTarget = errors.Is(err, netsafe.ErrPrivateAddressBlocked)
  196. res.Reason = "connection failed: " + err.Error()
  197. return res
  198. }
  199. defer conn.Close()
  200. if remote, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
  201. res.PrivateTarget = netsafe.IsBlockedIP(remote.IP)
  202. // The opt-in bypasses the SSRF guard, so leave an audit trail of it.
  203. if res.PrivateTarget && allowPrivate {
  204. logger.Infof("reality scan reached private target %s (%s) with the operator opt-in", addr, remote.IP)
  205. }
  206. }
  207. _ = conn.SetDeadline(time.Now().Add(timeout))
  208. // A REALITY inbound with xver>=1 fronts a target that speaks the PROXY
  209. // protocol (e.g. an Nginx listener with `proxy_protocol`), so the probe
  210. // must lead with a PROXY header or the target resets the connection and
  211. // the scan reports a spurious handshake failure (#6082).
  212. if xver >= 1 {
  213. if err := writeProxyProtocolHeader(conn, xver); err != nil {
  214. res.Reason = "proxy protocol write failed: " + err.Error()
  215. return res
  216. }
  217. }
  218. cfg := &tls.Config{
  219. ServerName: sni,
  220. InsecureSkipVerify: true,
  221. NextProtos: []string{"h2", "http/1.1"},
  222. CurvePreferences: []tls.CurveID{tls.X25519, tls.X25519MLKEM768},
  223. MinVersion: tls.VersionTLS12,
  224. }
  225. tlsConn := tls.Client(conn, cfg)
  226. if err := tlsConn.HandshakeContext(ctx); err != nil {
  227. res.Reason = "TLS handshake failed: " + err.Error()
  228. return res
  229. }
  230. res.LatencyMs = int(time.Since(start).Milliseconds())
  231. st := tlsConn.ConnectionState()
  232. res.TLS13 = st.Version == tls.VersionTLS13
  233. res.TLSVersion = tlsVersionName(st.Version)
  234. res.ALPN = st.NegotiatedProtocol
  235. res.H2 = st.NegotiatedProtocol == "h2"
  236. res.CurveID = realityCurveName(st.CurveID)
  237. res.X25519 = st.CurveID == tls.X25519 || st.CurveID == tls.X25519MLKEM768
  238. verifyHost := sni
  239. if len(st.PeerCertificates) > 0 {
  240. leaf := st.PeerCertificates[0]
  241. for _, cert := range st.PeerCertificates {
  242. res.CertChainBytes += len(cert.Raw)
  243. }
  244. res.CertSubject = leaf.Subject.CommonName
  245. if res.CertSubject == "" && len(leaf.DNSNames) > 0 {
  246. res.CertSubject = leaf.DNSNames[0]
  247. }
  248. if len(leaf.Issuer.Organization) > 0 {
  249. res.CertIssuer = leaf.Issuer.Organization[0]
  250. } else {
  251. res.CertIssuer = leaf.Issuer.CommonName
  252. }
  253. res.NotAfter = leaf.NotAfter.UTC().Format(time.RFC3339)
  254. res.ServerNames = filterUsableSANs(leaf.DNSNames)
  255. if sni == "" {
  256. if discovered := firstUsableName(leaf); discovered != "" {
  257. res.Host = discovered
  258. res.Target = net.JoinHostPort(discovered, strconv.Itoa(port))
  259. verifyHost = discovered
  260. }
  261. }
  262. if verifyHost != "" {
  263. opts := x509.VerifyOptions{Intermediates: x509.NewCertPool()}
  264. for _, c := range st.PeerCertificates[1:] {
  265. opts.Intermediates.AddCert(c)
  266. }
  267. // The chain is checked without the name first: a publicly trusted
  268. // certificate for other names still carries usable serverNames.
  269. res.CertChainValid = leafVerifies(leaf, opts)
  270. opts.DNSName = verifyHost
  271. if leafVerifies(leaf, opts) {
  272. res.CertValid = true
  273. } else {
  274. _, verr := leaf.Verify(opts)
  275. res.Reason = "certificate not trusted: " + verr.Error()
  276. }
  277. } else {
  278. res.Reason = "no usable domain in certificate"
  279. }
  280. } else {
  281. res.Reason = "no certificate presented"
  282. }
  283. res.Feasible = res.TLS13 && res.H2 && res.X25519 && res.CertValid
  284. if !res.Feasible && res.Reason == "" {
  285. switch {
  286. case !res.TLS13:
  287. res.Reason = "server does not negotiate TLS 1.3"
  288. case !res.H2:
  289. res.Reason = "server does not negotiate HTTP/2 (h2)"
  290. case !res.X25519:
  291. res.Reason = "server did not use X25519 key exchange"
  292. }
  293. }
  294. return res
  295. }
  296. // ScanRealityTarget probes one operator-supplied target. An empty sni falls back
  297. // to the target host; allowPrivate lifts the SSRF guard for this probe only.
  298. func (s *ServerService) ScanRealityTarget(target string, sni string, xver int, allowPrivate bool) (*RealityScanResult, error) {
  299. host, port, err := splitRealityTarget(target)
  300. if err != nil {
  301. return nil, err
  302. }
  303. sni = strings.TrimSpace(sni)
  304. if sni == "" {
  305. sni = host
  306. } else if sni, err = netsafe.NormalizeHost(sni); err != nil {
  307. return nil, common.NewError("invalid SNI: ", err)
  308. }
  309. return s.probeRealityAddr(host, port, sni, realityScanTimeout, xver, allowPrivate), nil
  310. }
  311. func parseRealityScanCandidateCSV(csv string) []string {
  312. var tokens []string
  313. for raw := range strings.SplitSeq(csv, ",") {
  314. if t := strings.TrimSpace(raw); t != "" {
  315. tokens = append(tokens, t)
  316. }
  317. }
  318. return tokens
  319. }
  320. // realityScanCandidateTokens returns the operator-configured candidate list,
  321. // falling back to the shipped defaults when the setting is empty or unreadable.
  322. func (s *ServerService) realityScanCandidateTokens() []string {
  323. csv, err := s.settingService.GetRealityScanCandidates()
  324. if err != nil {
  325. logger.Warning("reality scan: reading candidates setting failed:", err)
  326. } else if tokens := parseRealityScanCandidateCSV(csv); len(tokens) > 0 {
  327. return tokens
  328. }
  329. return append([]string(nil), defaultRealityScanCandidates...)
  330. }
  331. func (s *ServerService) ScanRealityTargets(targetsCSV string) ([]*RealityScanResult, error) {
  332. tokens := parseRealityScanCandidateCSV(targetsCSV)
  333. if len(tokens) == 0 {
  334. tokens = s.realityScanCandidateTokens()
  335. }
  336. var tasks []realityProbeTask
  337. var invalid []*RealityScanResult
  338. for _, token := range tokens {
  339. if len(tasks) >= realityScanMaxTotal {
  340. break
  341. }
  342. if strings.Contains(token, "/") {
  343. ips, err := enumerateCIDR(token, realityDiscoverMaxIPs)
  344. if err != nil {
  345. invalid = append(invalid, &RealityScanResult{Target: token, Reason: "invalid CIDR: " + err.Error()})
  346. continue
  347. }
  348. for _, ip := range ips {
  349. if len(tasks) >= realityScanMaxTotal {
  350. break
  351. }
  352. tasks = append(tasks, realityProbeTask{dialHost: ip, port: 443, timeout: realityDiscoverTimeout, bulk: true})
  353. }
  354. continue
  355. }
  356. host, port, err := splitRealityTarget(token)
  357. if err != nil {
  358. invalid = append(invalid, &RealityScanResult{Target: token, Reason: err.Error()})
  359. continue
  360. }
  361. if net.ParseIP(host) != nil {
  362. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, timeout: realityDiscoverTimeout})
  363. } else {
  364. tasks = append(tasks, realityProbeTask{dialHost: host, port: port, sni: host, timeout: realityScanTimeout})
  365. }
  366. }
  367. probed := make([]*RealityScanResult, len(tasks))
  368. sem := make(chan struct{}, realityScanConcurrency)
  369. var wg sync.WaitGroup
  370. for i, task := range tasks {
  371. wg.Add(1)
  372. sem <- struct{}{}
  373. go func(idx int, tk realityProbeTask) {
  374. defer wg.Done()
  375. defer func() { <-sem }()
  376. // The bulk/CIDR scanner never reaches private ranges: the opt-in
  377. // there would turn it into an internal network scanner.
  378. r := s.probeRealityAddr(tk.dialHost, tk.port, tk.sni, tk.timeout, 0, false)
  379. if tk.bulk && r.TLSVersion == "" {
  380. return
  381. }
  382. probed[idx] = r
  383. }(i, task)
  384. }
  385. wg.Wait()
  386. results := dedupRealityResults(append(probed, invalid...))
  387. sortRealityResults(results)
  388. return results, nil
  389. }
  390. func dedupRealityResults(results []*RealityScanResult) []*RealityScanResult {
  391. best := make(map[string]*RealityScanResult)
  392. order := make([]string, 0, len(results))
  393. for _, r := range results {
  394. if r == nil {
  395. continue
  396. }
  397. if ex, ok := best[r.Target]; !ok {
  398. best[r.Target] = r
  399. order = append(order, r.Target)
  400. } else if betterRealityResult(r, ex) {
  401. best[r.Target] = r
  402. }
  403. }
  404. out := make([]*RealityScanResult, 0, len(order))
  405. for _, k := range order {
  406. out = append(out, best[k])
  407. }
  408. return out
  409. }
  410. func betterRealityResult(a, b *RealityScanResult) bool {
  411. if a.Feasible != b.Feasible {
  412. return a.Feasible
  413. }
  414. return a.LatencyMs > 0 && (b.LatencyMs == 0 || a.LatencyMs < b.LatencyMs)
  415. }
  416. func sortRealityResults(results []*RealityScanResult) {
  417. slices.SortStableFunc(results, func(a, b *RealityScanResult) int {
  418. if a.Feasible != b.Feasible {
  419. if a.Feasible {
  420. return -1
  421. }
  422. return 1
  423. }
  424. return a.LatencyMs - b.LatencyMs
  425. })
  426. }
  427. // writeProxyProtocolHeader emits a PROXY protocol header describing the local
  428. // connection so a target that requires it (Nginx `proxy_protocol`, matching a
  429. // REALITY inbound's xver) accepts the probe instead of resetting it. xver 1
  430. // sends the human-readable v1 header; xver 2 sends the binary v2 header. The
  431. // addresses come from the already-dialed connection, so they are always a
  432. // consistent, real (src, dst) pair.
  433. func writeProxyProtocolHeader(conn net.Conn, xver int) error {
  434. local, lok := conn.LocalAddr().(*net.TCPAddr)
  435. remote, rok := conn.RemoteAddr().(*net.TCPAddr)
  436. if !lok || !rok {
  437. return fmt.Errorf("connection has no TCP addresses")
  438. }
  439. if xver >= 2 {
  440. return writeProxyProtocolV2(conn, local, remote)
  441. }
  442. return writeProxyProtocolV1(conn, local, remote)
  443. }
  444. func writeProxyProtocolV1(conn net.Conn, local, remote *net.TCPAddr) error {
  445. fam := "TCP4"
  446. if local.IP.To4() == nil || remote.IP.To4() == nil {
  447. fam = "TCP6"
  448. }
  449. header := fmt.Sprintf("PROXY %s %s %s %d %d\r\n", fam, local.IP.String(), remote.IP.String(), local.Port, remote.Port)
  450. _, err := conn.Write([]byte(header))
  451. return err
  452. }
  453. func writeProxyProtocolV2(conn net.Conn, local, remote *net.TCPAddr) error {
  454. buf := []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}
  455. buf = append(buf, 0x21)
  456. src4, dst4 := local.IP.To4(), remote.IP.To4()
  457. if src4 != nil && dst4 != nil {
  458. buf = append(buf, 0x11)
  459. buf = append(buf, 0x00, 12)
  460. buf = append(buf, src4...)
  461. buf = append(buf, dst4...)
  462. buf = append(buf, byte(local.Port>>8), byte(local.Port))
  463. buf = append(buf, byte(remote.Port>>8), byte(remote.Port))
  464. } else {
  465. buf = append(buf, 0x21)
  466. buf = append(buf, 0x00, 36)
  467. buf = append(buf, local.IP.To16()...)
  468. buf = append(buf, remote.IP.To16()...)
  469. buf = append(buf, byte(local.Port>>8), byte(local.Port))
  470. buf = append(buf, byte(remote.Port>>8), byte(remote.Port))
  471. }
  472. _, err := conn.Write(buf)
  473. return err
  474. }