speed_tester.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. package core
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "io"
  7. "math/rand"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "regexp"
  12. "sort"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "time"
  17. "clash-speed-test/internal/config"
  18. "clash-speed-test/internal/database"
  19. applogger "clash-speed-test/internal/logger"
  20. )
  21. type SpeedTester struct {
  22. config *config.Config
  23. testURLs []string
  24. timeout time.Duration
  25. concurrency int
  26. httpClient *http.Client
  27. }
  28. type SpeedTestResult struct {
  29. Latency int `json:"latency"`
  30. DownloadSpeed float64 `json:"download_speed"` // Mbps
  31. UploadSpeed float64 `json:"upload_speed"` // Mbps
  32. IPAddress string `json:"ip_address"`
  33. Location string `json:"location"`
  34. Success bool `json:"success"`
  35. ErrorMessage string `json:"error_message"`
  36. }
  37. func NewSpeedTester(cfg *config.Config) *SpeedTester {
  38. // 创建HTTP客户端
  39. httpClient := &http.Client{
  40. Timeout: cfg.Speed.Timeout,
  41. Transport: &http.Transport{
  42. TLSClientConfig: &tls.Config{
  43. InsecureSkipVerify: false,
  44. },
  45. DisableKeepAlives: true,
  46. MaxIdleConns: 100,
  47. IdleConnTimeout: 30 * time.Second,
  48. },
  49. }
  50. return &SpeedTester{
  51. config: cfg,
  52. testURLs: cfg.Speed.TestURLs,
  53. timeout: cfg.Speed.Timeout,
  54. concurrency: cfg.Speed.Concurrency,
  55. httpClient: httpClient,
  56. }
  57. }
  58. // 测试单个节点
  59. func (st *SpeedTester) TestNode(node database.Node) (*database.TestResult, error) {
  60. startTime := time.Now()
  61. result := &database.TestResult{
  62. NodeID: node.ID,
  63. TestTime: time.Now(),
  64. IsSuccess: false,
  65. }
  66. applogger.Info("开始测试节点", map[string]interface{}{
  67. "name": node.Name,
  68. "type": node.Type,
  69. "server": node.Server,
  70. "port": node.Port,
  71. })
  72. // 验证节点配置的有效性
  73. if err := st.validateNode(node); err != nil {
  74. result.ErrorMessage = fmt.Sprintf("节点配置无效: %v", err)
  75. applogger.Warn("节点配置无效", map[string]interface{}{
  76. "node": node.Name,
  77. "error": err.Error(),
  78. })
  79. return result, nil
  80. }
  81. // 测试连接性和速度
  82. speedResult, err := st.testNodeSpeed(node)
  83. if err != nil {
  84. result.ErrorMessage = fmt.Sprintf("测速失败: %v", err)
  85. applogger.Warn("节点测试失败", map[string]interface{}{
  86. "node": node.Name,
  87. "error": err.Error(),
  88. })
  89. } else if speedResult.Success {
  90. result.IsSuccess = true
  91. result.Latency = &speedResult.Latency
  92. result.IPAddress = speedResult.IPAddress
  93. result.Location = speedResult.Location
  94. result.TestURL = st.testURLs[0]
  95. // 如果有速度数据,保存到扩展字段
  96. if speedResult.DownloadSpeed > 0 {
  97. downloadSpeed := int(speedResult.DownloadSpeed)
  98. result.DownloadSpeed = &downloadSpeed
  99. }
  100. if speedResult.UploadSpeed > 0 {
  101. uploadSpeed := int(speedResult.UploadSpeed)
  102. result.UploadSpeed = &uploadSpeed
  103. }
  104. applogger.Info("节点测试成功", map[string]interface{}{
  105. "node": node.Name,
  106. "latency": speedResult.Latency,
  107. "download_speed": speedResult.DownloadSpeed,
  108. "upload_speed": speedResult.UploadSpeed,
  109. "ip": speedResult.IPAddress,
  110. "location": speedResult.Location,
  111. })
  112. } else {
  113. result.ErrorMessage = speedResult.ErrorMessage
  114. applogger.Warn("节点测试失败", map[string]interface{}{
  115. "node": node.Name,
  116. "error": speedResult.ErrorMessage,
  117. })
  118. }
  119. // 计算测试时长
  120. duration := int(time.Since(startTime).Milliseconds())
  121. result.TestDuration = &duration
  122. // 保存测试结果
  123. if err := database.SaveTestResult(result); err != nil {
  124. applogger.Error("保存测试结果失败", map[string]interface{}{
  125. "node": node.Name,
  126. "error": err.Error(),
  127. })
  128. }
  129. return result, nil
  130. }
  131. // 验证节点配置的有效性
  132. func (st *SpeedTester) validateNode(node database.Node) error {
  133. // 检查必需的字段
  134. if node.Name == "" {
  135. return fmt.Errorf("节点名称不能为空")
  136. }
  137. if node.Type == "" {
  138. return fmt.Errorf("节点类型不能为空")
  139. }
  140. if node.Server == "" {
  141. return fmt.Errorf("服务器地址不能为空")
  142. }
  143. if node.Port <= 0 || node.Port > 65535 {
  144. return fmt.Errorf("端口号无效: %d", node.Port)
  145. }
  146. // 验证server字段是否为有效的域名或IP地址
  147. server := strings.TrimSpace(node.Server)
  148. // 检查是否为空或只包含空白字符
  149. if server == "" {
  150. return fmt.Errorf("服务器地址不能为空")
  151. }
  152. // 检查是否是URL格式(不应该作为server地址)
  153. if strings.HasPrefix(server, "http://") || strings.HasPrefix(server, "https://") {
  154. return fmt.Errorf("服务器地址不能是URL格式: %s", server)
  155. }
  156. // 检查是否包含明显的无效字符(如空格、特殊符号等)
  157. invalidChars := regexp.MustCompile(`[<>:"\\|?*]`)
  158. if invalidChars.MatchString(server) {
  159. return fmt.Errorf("服务器地址包含无效字符: %s", server)
  160. }
  161. // 允许包含中文的服务器地址
  162. if st.containsChinese(server) {
  163. // 包含中文的地址直接通过验证
  164. applogger.Debug("检测到包含中文的服务器地址", map[string]interface{}{
  165. "domain": server,
  166. })
  167. }
  168. // 对于SS/SSR节点,检查密码
  169. if (node.Type == "ss" || node.Type == "ssr") && node.Password == "" {
  170. return fmt.Errorf("SS/SSR节点必须设置密码")
  171. }
  172. // 对于Vmess节点,检查UUID
  173. if node.Type == "vmess" && node.UUID == "" {
  174. return fmt.Errorf("Vmess节点必须设置UUID")
  175. }
  176. // 对于Trojan节点,检查密码
  177. if node.Type == "trojan" && node.Password == "" {
  178. return fmt.Errorf("Trojan节点必须设置密码")
  179. }
  180. return nil
  181. }
  182. // 检查字符串是否包含中文字符
  183. func (st *SpeedTester) containsChinese(s string) bool {
  184. for _, r := range s {
  185. if r >= 0x4e00 && r <= 0x9fff {
  186. return true
  187. }
  188. }
  189. return false
  190. }
  191. // 验证是否为合法的中文域名
  192. // 支持以下格式:
  193. // - 纯中文域名:中文.域名.com
  194. // - 混合域名:中文.example.com
  195. // - 包含中文的域名:test.中文.com
  196. func (st *SpeedTester) isValidChineseDomain(domain string) bool {
  197. // 移除前后空格
  198. domain = strings.TrimSpace(domain)
  199. // 检查是否包含中文字符
  200. if !st.containsChinese(domain) {
  201. return false
  202. }
  203. // 检查是否包含域名分隔符(点号)
  204. if !strings.Contains(domain, ".") {
  205. return false
  206. }
  207. // 检查是否以点号开头或结尾
  208. if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
  209. return false
  210. }
  211. // 检查是否包含连续的点号
  212. if strings.Contains(domain, "..") {
  213. return false
  214. }
  215. // 检查每个标签的长度(域名标签不能超过63个字符)
  216. labels := strings.Split(domain, ".")
  217. for _, label := range labels {
  218. if len(label) == 0 || len(label) > 63 {
  219. return false
  220. }
  221. }
  222. // 检查顶级域名(最后一部分)是否至少包含一个非中文字符
  223. tld := labels[len(labels)-1]
  224. if !regexp.MustCompile(`[a-zA-Z0-9]`).MatchString(tld) {
  225. return false
  226. }
  227. // 检查是否包含有效的域名字符(中文、英文、数字、连字符)
  228. validDomainRegex := regexp.MustCompile(`^[a-zA-Z0-9\u4e00-\u9fa5\-\.]+$`)
  229. if !validDomainRegex.MatchString(domain) {
  230. return false
  231. }
  232. return true
  233. }
  234. // 批量测试节点 - 改进版
  235. func (st *SpeedTester) TestNodes(nodes []database.Node) []*database.TestResult {
  236. var results []*database.TestResult
  237. var mu sync.Mutex
  238. var wg sync.WaitGroup
  239. // 降低默认并发数,提高稳定性
  240. concurrency := st.concurrency
  241. if concurrency > 3 {
  242. concurrency = 3
  243. }
  244. // 创建信号量控制并发数
  245. semaphore := make(chan struct{}, concurrency)
  246. applogger.Info("开始批量测试节点", map[string]interface{}{
  247. "total": len(nodes),
  248. "concurrency": concurrency,
  249. })
  250. startTime := time.Now()
  251. for i, node := range nodes {
  252. wg.Add(1)
  253. go func(n database.Node, index int) {
  254. defer wg.Done()
  255. // 添加随机延迟,避免同时发起请求
  256. randomDelay := time.Duration(rand.Intn(500)) * time.Millisecond
  257. time.Sleep(randomDelay)
  258. // 获取信号量
  259. semaphore <- struct{}{}
  260. defer func() { <-semaphore }()
  261. applogger.Debug("开始测试节点", map[string]interface{}{
  262. "node": n.Name,
  263. "index": index + 1,
  264. "total": len(nodes),
  265. })
  266. result, err := st.TestNode(n)
  267. if err != nil {
  268. applogger.Error("节点测试异常", map[string]interface{}{
  269. "node": n.Name,
  270. "error": err.Error(),
  271. })
  272. return
  273. }
  274. mu.Lock()
  275. results = append(results, result)
  276. mu.Unlock()
  277. applogger.Debug("节点测试完成", map[string]interface{}{
  278. "node": n.Name,
  279. "success": result.IsSuccess,
  280. "latency": result.Latency,
  281. "duration": time.Since(startTime),
  282. })
  283. }(node, i)
  284. }
  285. wg.Wait()
  286. // 按延迟排序
  287. sort.Slice(results, func(i, j int) bool {
  288. if results[i].IsSuccess && results[j].IsSuccess {
  289. return results[i].Latency < results[j].Latency
  290. }
  291. return results[i].IsSuccess
  292. })
  293. successCount := 0
  294. for _, result := range results {
  295. if result.IsSuccess {
  296. successCount++
  297. }
  298. }
  299. totalTime := time.Since(startTime)
  300. applogger.Info("批量测试完成", map[string]interface{}{
  301. "total": len(results),
  302. "successful": successCount,
  303. "failed": len(results) - successCount,
  304. "duration": totalTime,
  305. })
  306. return results
  307. }
  308. // 测试节点速度
  309. func (st *SpeedTester) testNodeSpeed(node database.Node) (*SpeedTestResult, error) {
  310. result := &SpeedTestResult{
  311. Success: false,
  312. }
  313. // 根据代理类型选择测试策略
  314. switch node.Type {
  315. case "http", "https":
  316. return st.testHTTPProxy(node)
  317. case "socks5":
  318. return st.testSOCKS5Proxy(node)
  319. case "ss", "ssr", "vmess", "trojan":
  320. // 对于高级代理,尝试多种测试方法
  321. return st.testAdvancedProxy(node)
  322. default:
  323. return result, fmt.Errorf("暂不支持代理类型: %s", node.Type)
  324. }
  325. }
  326. // 测试HTTP代理
  327. func (st *SpeedTester) testHTTPProxy(node database.Node) (*SpeedTestResult, error) {
  328. result := &SpeedTestResult{}
  329. // 构建代理URL
  330. proxyURL := fmt.Sprintf("http://%s:%d", node.Server, node.Port)
  331. if node.Username != "" && node.Password != "" {
  332. proxyURL = fmt.Sprintf("http://%s:%s@%s:%d", node.Username, node.Password, node.Server, node.Port)
  333. }
  334. // 创建代理客户端
  335. proxyURLParsed, err := url.Parse(proxyURL)
  336. if err != nil {
  337. return result, fmt.Errorf("解析代理URL失败: %w", err)
  338. }
  339. proxyClient := &http.Client{
  340. Timeout: st.timeout,
  341. Transport: &http.Transport{
  342. Proxy: http.ProxyURL(proxyURLParsed),
  343. TLSClientConfig: &tls.Config{
  344. InsecureSkipVerify: false,
  345. },
  346. DisableKeepAlives: true,
  347. },
  348. }
  349. // 测试延迟
  350. latency, ipAddress, location, err := st.testLatency(proxyClient)
  351. if err != nil {
  352. return result, fmt.Errorf("延迟测试失败: %w", err)
  353. }
  354. result.Latency = latency
  355. result.IPAddress = ipAddress
  356. result.Location = location
  357. // 测试下载速度
  358. downloadSpeed, err := st.testDownloadSpeed(proxyClient)
  359. if err != nil {
  360. applogger.Warn("下载速度测试失败", map[string]interface{}{
  361. "node": node.Name,
  362. "error": err.Error(),
  363. })
  364. } else {
  365. result.DownloadSpeed = downloadSpeed
  366. }
  367. // 测试上传速度
  368. uploadSpeed, err := st.testUploadSpeed(proxyClient)
  369. if err != nil {
  370. applogger.Warn("上传速度测试失败", map[string]interface{}{
  371. "node": node.Name,
  372. "error": err.Error(),
  373. })
  374. } else {
  375. result.UploadSpeed = uploadSpeed
  376. }
  377. result.Success = true
  378. return result, nil
  379. }
  380. // 测试SOCKS5代理
  381. func (st *SpeedTester) testSOCKS5Proxy(node database.Node) (*SpeedTestResult, error) {
  382. result := &SpeedTestResult{}
  383. // 构建SOCKS5代理URL
  384. proxyURL := fmt.Sprintf("socks5://%s:%d", node.Server, node.Port)
  385. if node.Username != "" && node.Password != "" {
  386. proxyURL = fmt.Sprintf("socks5://%s:%s@%s:%d", node.Username, node.Password, node.Server, node.Port)
  387. }
  388. // 创建代理客户端
  389. proxyURLParsed, err := url.Parse(proxyURL)
  390. if err != nil {
  391. return result, fmt.Errorf("解析SOCKS5代理URL失败: %w", err)
  392. }
  393. proxyClient := &http.Client{
  394. Timeout: st.timeout,
  395. Transport: &http.Transport{
  396. Proxy: http.ProxyURL(proxyURLParsed),
  397. TLSClientConfig: &tls.Config{
  398. InsecureSkipVerify: false,
  399. },
  400. DisableKeepAlives: true,
  401. },
  402. }
  403. // 测试延迟
  404. latency, ipAddress, location, err := st.testLatency(proxyClient)
  405. if err != nil {
  406. return result, fmt.Errorf("延迟测试失败: %w", err)
  407. }
  408. result.Latency = latency
  409. result.IPAddress = ipAddress
  410. result.Location = location
  411. // 测试下载速度
  412. downloadSpeed, err := st.testDownloadSpeed(proxyClient)
  413. if err != nil {
  414. applogger.Warn("下载速度测试失败", map[string]interface{}{
  415. "node": node.Name,
  416. "error": err.Error(),
  417. })
  418. } else {
  419. result.DownloadSpeed = downloadSpeed
  420. }
  421. // 测试上传速度
  422. uploadSpeed, err := st.testUploadSpeed(proxyClient)
  423. if err != nil {
  424. applogger.Warn("上传速度测试失败", map[string]interface{}{
  425. "node": node.Name,
  426. "error": err.Error(),
  427. })
  428. } else {
  429. result.UploadSpeed = uploadSpeed
  430. }
  431. result.Success = true
  432. return result, nil
  433. }
  434. // 测试高级代理(Shadowsocks、Vmess等)
  435. func (st *SpeedTester) testAdvancedProxy(node database.Node) (*SpeedTestResult, error) {
  436. result := &SpeedTestResult{}
  437. // 对于高级代理,我们需要通过本地Clash代理来测试
  438. // 但首先需要验证本地代理是否可用,以及是否真的配置了这个节点
  439. // 对于高级代理,我们尝试通过本地代理端口进行测试
  440. // 如果本地代理不可用,会在测试过程中失败,这是正常的
  441. // 尝试常见的本地代理端口
  442. localProxyPorts := []int{7890, 7891, 1080, 8080, 8118}
  443. for _, port := range localProxyPorts {
  444. proxyURL := fmt.Sprintf("http://127.0.0.1:%d", port)
  445. proxyURLParsed, err := url.Parse(proxyURL)
  446. if err != nil {
  447. continue
  448. }
  449. proxyClient := &http.Client{
  450. Timeout: st.timeout,
  451. Transport: &http.Transport{
  452. Proxy: http.ProxyURL(proxyURLParsed),
  453. TLSClientConfig: &tls.Config{
  454. InsecureSkipVerify: false,
  455. },
  456. DisableKeepAlives: true,
  457. },
  458. }
  459. // 测试延迟
  460. latency, ipAddress, location, err := st.testLatency(proxyClient)
  461. if err == nil {
  462. result.Latency = latency
  463. result.IPAddress = ipAddress
  464. result.Location = location
  465. result.Success = true
  466. // 测试下载速度
  467. if downloadSpeed, err := st.testDownloadSpeed(proxyClient); err == nil {
  468. result.DownloadSpeed = downloadSpeed
  469. }
  470. // 测试上传速度
  471. if uploadSpeed, err := st.testUploadSpeed(proxyClient); err == nil {
  472. result.UploadSpeed = uploadSpeed
  473. }
  474. applogger.Info("通过本地代理测试成功", map[string]interface{}{
  475. "node": node.Name,
  476. "port": port,
  477. })
  478. return result, nil
  479. }
  480. }
  481. return result, fmt.Errorf("无法通过本地代理测试节点: %s", node.Name)
  482. }
  483. // 测试延迟
  484. func (st *SpeedTester) testLatency(client *http.Client) (int, string, string, error) {
  485. startTime := time.Now()
  486. // 尝试多个测试URL
  487. for _, testURL := range st.testURLs {
  488. req, err := http.NewRequest("HEAD", testURL, nil)
  489. if err != nil {
  490. continue
  491. }
  492. req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
  493. resp, err := client.Do(req)
  494. if err != nil {
  495. continue
  496. }
  497. defer resp.Body.Close()
  498. latency := int(time.Since(startTime).Milliseconds())
  499. ipAddress := st.extractIPAddress(resp)
  500. location := st.extractLocation(resp)
  501. return latency, ipAddress, location, nil
  502. }
  503. return 0, "", "", fmt.Errorf("所有测试URL都无法访问")
  504. }
  505. // 测试下载速度
  506. func (st *SpeedTester) testDownloadSpeed(client *http.Client) (float64, error) {
  507. // 使用1MB的测试文件
  508. testURL := "https://httpbin.org/bytes/1048576"
  509. startTime := time.Now()
  510. req, err := http.NewRequest("GET", testURL, nil)
  511. if err != nil {
  512. return 0, fmt.Errorf("创建请求失败: %w", err)
  513. }
  514. resp, err := client.Do(req)
  515. if err != nil {
  516. return 0, fmt.Errorf("下载测试失败: %w", err)
  517. }
  518. defer resp.Body.Close()
  519. // 读取响应体
  520. body, err := io.ReadAll(resp.Body)
  521. if err != nil {
  522. return 0, fmt.Errorf("读取响应失败: %w", err)
  523. }
  524. duration := time.Since(startTime).Seconds()
  525. fileSize := len(body)
  526. // 计算下载速度 (Mbps)
  527. speedBps := float64(fileSize) / duration
  528. speedMbps := (speedBps * 8) / (1024 * 1024)
  529. return speedMbps, nil
  530. }
  531. // 测试上传速度
  532. func (st *SpeedTester) testUploadSpeed(client *http.Client) (float64, error) {
  533. // 使用1MB的测试数据
  534. testData := strings.Repeat("A", 1024*1024)
  535. startTime := time.Now()
  536. req, err := http.NewRequest("POST", "https://httpbin.org/post", strings.NewReader(testData))
  537. if err != nil {
  538. return 0, fmt.Errorf("创建请求失败: %w", err)
  539. }
  540. req.Header.Set("Content-Type", "application/octet-stream")
  541. resp, err := client.Do(req)
  542. if err != nil {
  543. return 0, fmt.Errorf("上传测试失败: %w", err)
  544. }
  545. defer resp.Body.Close()
  546. duration := time.Since(startTime).Seconds()
  547. fileSize := len(testData)
  548. // 计算上传速度 (Mbps)
  549. speedBps := float64(fileSize) / duration
  550. speedMbps := (speedBps * 8) / (1024 * 1024)
  551. return speedMbps, nil
  552. }
  553. // 提取IP地址
  554. func (st *SpeedTester) extractIPAddress(resp *http.Response) string {
  555. if ip := resp.Header.Get("X-Forwarded-For"); ip != "" {
  556. return strings.Split(ip, ",")[0]
  557. }
  558. if ip := resp.Header.Get("X-Real-IP"); ip != "" {
  559. return ip
  560. }
  561. if ip := resp.Header.Get("CF-Connecting-IP"); ip != "" {
  562. return ip
  563. }
  564. return "unknown"
  565. }
  566. // 提取位置信息
  567. func (st *SpeedTester) extractLocation(resp *http.Response) string {
  568. if resp.Header.Get("CF-Ray") != "" {
  569. return "Cloudflare"
  570. }
  571. if country := resp.Header.Get("CF-IPCountry"); country != "" {
  572. return country
  573. }
  574. return "unknown"
  575. }