77 "net"
88 "regexp"
99 "strings"
10+ "time"
1011
1112 "github.com/google/uuid"
1213 "github.com/imroc/req/v3"
@@ -41,14 +42,48 @@ type PoPResult struct {
4142 Result string
4243}
4344
45+ // retryConfig 重试配置
46+ type retryConfig struct {
47+ maxRetries int
48+ timeouts []time.Duration
49+ }
50+
51+ // 默认重试配置:3次重试,超时时间分别为3s、4s、5s
52+ var defaultRetryConfig = retryConfig {
53+ maxRetries : 3 ,
54+ timeouts : []time.Duration {3 * time .Second , 4 * time .Second , 5 * time .Second },
55+ }
56+
57+ // executeWithRetry 执行带重试的HTTP请求
58+ func executeWithRetry (client * req.Client , url string , config retryConfig ) (* req.Response , error ) {
59+ var lastErr error
60+ for attempt := 0 ; attempt < config .maxRetries ; attempt ++ {
61+ timeout := config .timeouts [attempt ]
62+ resp , err := client .SetTimeout (timeout ).R ().
63+ Get (url )
64+ if err == nil && resp .StatusCode == 200 {
65+ return resp , nil
66+ }
67+ if err != nil {
68+ lastErr = fmt .Errorf ("attempt %d failed with timeout %v: %w" , attempt + 1 , timeout , err )
69+ } else {
70+ lastErr = fmt .Errorf ("attempt %d failed with HTTP status %d (timeout %v)" , attempt + 1 , resp .StatusCode , timeout )
71+ }
72+ if attempt < config .maxRetries - 1 {
73+ time .Sleep (3 * time .Second )
74+ }
75+ }
76+ return nil , fmt .Errorf ("all %d attempts failed, last error: %w" , config .maxRetries , lastErr )
77+ }
78+
4479func getISPAbbr (asn , name string ) string {
4580 if abbr , ok := model .Tier1Global [asn ]; ok {
4681 return abbr
4782 }
48- if idx := strings .Index (name , " " ); idx != - 1 && idx > 18 {
83+ if idx := strings .Index (name , " " ); idx != - 1 && idx >= 18 {
4984 return name [:idx ]
5085 }
51- return name
86+ return strings . TrimSpace ( name )
5287}
5388
5489func getISPType (asn string , tier1 bool , direct bool ) string {
@@ -74,42 +109,54 @@ func isValidIP(ip string) bool {
74109 return net .ParseIP (ip ) != nil
75110}
76111
77- func getSVGPath (client * req. Client , ip string ) (string , error ) {
112+ func getSVGPath (ip string ) (string , error ) {
78113 if ! isValidIP (ip ) {
79114 return "" , fmt .Errorf ("invalid IP address: %s" , ip )
80115 }
81- url := fmt .Sprintf ("https://bgp.tools/prefix/%s#connectivity" , ip )
82- resp , err := client .R ().Get (url )
83- if err != nil {
84- return "" , fmt .Errorf ("failed to fetch BGP info for IP %s: %w" , ip , err )
85- }
86- if resp .StatusCode != 200 {
87- return "" , fmt .Errorf ("HTTP error %d when fetching BGP info for IP %s" , resp .StatusCode , ip )
88- }
89- body := resp .String ()
90- re := regexp .MustCompile (`<img[^>]+id="pathimg"[^>]+src="([^"]+)"` )
91- matches := re .FindStringSubmatch (body )
92- if len (matches ) < 2 {
93- return "" , fmt .Errorf ("SVG path not found for IP %s" , ip )
116+ var lastErr error
117+ for attempt := 0 ; attempt < defaultRetryConfig .maxRetries ; attempt ++ {
118+ client := req .C ().ImpersonateChrome ()
119+ url := fmt .Sprintf ("https://bgp.tools/prefix/%s#connectivity" , ip )
120+ resp , err := executeWithRetry (client , url , defaultRetryConfig )
121+ if err == nil {
122+ body := resp .String ()
123+ re := regexp .MustCompile (`<img[^>]+id="pathimg"[^>]+src="([^"]+)"` )
124+ matches := re .FindStringSubmatch (body )
125+ if len (matches ) >= 2 {
126+ return matches [1 ], nil
127+ }
128+ lastErr = fmt .Errorf ("SVG path not found for IP %s" , ip )
129+ } else {
130+ lastErr = fmt .Errorf ("failed to fetch BGP info for IP %s: %w" , ip , err )
131+ }
132+ if attempt < defaultRetryConfig .maxRetries - 1 {
133+ time .Sleep (1 * time .Second )
134+ }
94135 }
95- return matches [ 1 ], nil
136+ return "" , fmt . Errorf ( "failed to get SVG path after %d retries: %w" , defaultRetryConfig . maxRetries , lastErr )
96137}
97138
98- func downloadSVG (client * req.Client , svgPath string ) (string , error ) {
99- uuid := uuid .NewString ()
100- url := fmt .Sprintf ("https://bgp.tools%s?%s&loggedin" , svgPath , uuid )
101- resp , err := client .R ().Get (url )
102- if err != nil {
103- return "" , fmt .Errorf ("failed to download SVG: %w" , err )
104- }
105- if resp .StatusCode != 200 {
106- return "" , fmt .Errorf ("HTTP error %d when downloading SVG" , resp .StatusCode )
107- }
108- bodyBytes , err := io .ReadAll (resp .Body )
109- if err != nil {
110- return "" , fmt .Errorf ("failed to read SVG response body: %w" , err )
139+ func downloadSVG (svgPath string ) (string , error ) {
140+ var lastErr error
141+ for attempt := 0 ; attempt < defaultRetryConfig .maxRetries ; attempt ++ {
142+ client := req .C ().ImpersonateChrome ()
143+ uuid := uuid .NewString ()
144+ url := fmt .Sprintf ("https://bgp.tools%s?%s&loggedin" , svgPath , uuid )
145+ resp , err := executeWithRetry (client , url , defaultRetryConfig )
146+ if err == nil {
147+ bodyBytes , err := io .ReadAll (resp .Body )
148+ if err == nil {
149+ return string (bodyBytes ), nil
150+ }
151+ lastErr = fmt .Errorf ("failed to read SVG response body: %w" , err )
152+ } else {
153+ lastErr = fmt .Errorf ("failed to download SVG: %w" , err )
154+ }
155+ if attempt < defaultRetryConfig .maxRetries - 1 {
156+ time .Sleep (1 * time .Second )
157+ }
111158 }
112- return string ( bodyBytes ), nil
159+ return "" , fmt . Errorf ( "failed to download SVG after %d retries: %w" , defaultRetryConfig . maxRetries , lastErr )
113160}
114161
115162func parseASAndEdges (svg string ) ([]ASCard , []Arrow ) {
@@ -183,6 +230,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
183230 }
184231 }
185232 var upstreams []Upstream
233+ addedASNs := map [string ]bool {}
186234 for _ , n := range nodes {
187235 if ! upstreamMap [n .ASN ] {
188236 continue
@@ -196,6 +244,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
196244 Tier1 : isTier1 ,
197245 Type : upstreamType ,
198246 })
247+ addedASNs [n .ASN ] = true
199248 }
200249 if len (upstreams ) == 1 {
201250 currentASN := upstreams [0 ].ASN
@@ -214,14 +263,7 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
214263 nextASN = asn
215264 break
216265 }
217- found := false
218- for _ , existing := range upstreams {
219- if existing .ASN == nextASN {
220- found = true
221- break
222- }
223- }
224- if found {
266+ if addedASNs [nextASN ] {
225267 break
226268 }
227269 var nextNode * ASCard
@@ -243,8 +285,56 @@ func findUpstreams(targetASN string, nodes []ASCard, edges []Arrow) []Upstream {
243285 Tier1 : isTier1 ,
244286 Type : upstreamType ,
245287 })
288+ addedASNs [nextNode .ASN ] = true
246289 currentASN = nextASN
247290 }
291+ } else if len (upstreams ) > 1 {
292+ for _ , directUpstream := range upstreams {
293+ currentASN := directUpstream .ASN
294+ for {
295+ nextUpstreams := map [string ]bool {}
296+ for _ , e := range edges {
297+ if e .From == currentASN {
298+ nextUpstreams [e .To ] = true
299+ }
300+ }
301+ if len (nextUpstreams ) != 1 {
302+ break
303+ }
304+ var nextASN string
305+ for asn := range nextUpstreams {
306+ nextASN = asn
307+ break
308+ }
309+ if addedASNs [nextASN ] {
310+ break
311+ }
312+ var nextNode * ASCard
313+ for _ , n := range nodes {
314+ if n .ASN == nextASN {
315+ nextNode = & n
316+ break
317+ }
318+ }
319+ if nextNode == nil {
320+ break
321+ }
322+ isTier1 := (nextNode .Fill == "white" && nextNode .Stroke == "#005ea5" )
323+ if isTier1 {
324+ upstreamType := getISPType (nextNode .ASN , isTier1 , false )
325+ upstreams = append (upstreams , Upstream {
326+ ASN : nextNode .ASN ,
327+ Name : nextNode .Name ,
328+ Direct : false ,
329+ Tier1 : isTier1 ,
330+ Type : upstreamType ,
331+ })
332+ addedASNs [nextNode .ASN ] = true
333+ break
334+ }
335+ currentASN = nextASN
336+ }
337+ }
248338 }
249339 return upstreams
250340}
@@ -253,12 +343,12 @@ func GetPoPInfo(ip string) (*PoPResult, error) {
253343 if ip == "" {
254344 return nil , fmt .Errorf ("IP address cannot be empty" )
255345 }
256- client := req . C (). ImpersonateChrome ()
257- svgPath , err := getSVGPath (client , ip )
346+
347+ svgPath , err := getSVGPath (ip )
258348 if err != nil {
259349 return nil , fmt .Errorf ("获取SVG路径失败: %w" , err )
260350 }
261- svg , err := downloadSVG (client , svgPath )
351+ svg , err := downloadSVG (svgPath )
262352 if err != nil {
263353 return nil , fmt .Errorf ("下载SVG失败: %w" , err )
264354 }
0 commit comments