github.com/GuanceCloud/cliutils@v1.1.21/dialtesting/icmp.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package dialtesting 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "net" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/GuanceCloud/cliutils" 17 "github.com/go-ping/ping" 18 ) 19 20 const ( 21 PingTimeout = 3 * time.Second 22 ) 23 24 type ICMP struct { 25 Type uint8 26 Code uint8 27 Checksum uint16 28 Identifier uint16 29 SequenceNum uint16 30 } 31 32 type ResponseTimeSucess struct { 33 Func string `json:"func,omitempty"` 34 Op string `json:"op,omitempty"` 35 Target string `json:"target,omitempty"` 36 37 target float64 38 } 39 40 type ICMPSuccess struct { 41 PacketLossPercent []*ValueSuccess `json:"packet_loss_percent,omitempty"` 42 ResponseTime []*ResponseTimeSucess `json:"response_time,omitempty"` 43 Hops []*ValueSuccess `json:"hops,omitempty"` 44 Packets []*ValueSuccess `json:"packets,omitempty"` 45 } 46 47 type ICMPTask struct { 48 Host string `json:"host"` 49 PacketCount int `json:"packet_count"` 50 Timeout string `json:"timeout"` 51 EnableTraceroute bool `json:"enable_traceroute"` 52 TracerouteConfig *TracerouteOption `json:"traceroute_config"` 53 SuccessWhen []*ICMPSuccess `json:"success_when"` 54 SuccessWhenLogic string `json:"success_when_logic"` 55 ExternalID string `json:"external_id"` 56 Name string `json:"name"` 57 AK string `json:"access_key"` 58 PostURL string `json:"post_url"` 59 CurStatus string `json:"status"` 60 Frequency string `json:"frequency"` 61 Region string `json:"region"` 62 OwnerExternalID string `json:"owner_external_id"` 63 Tags map[string]string `json:"tags,omitempty"` 64 Labels []string `json:"labels,omitempty"` 65 UpdateTime int64 `json:"update_time,omitempty"` 66 WorkspaceLanguage string `json:"workspace_language,omitempty"` 67 TagsInfo string `json:"tags_info,omitempty"` 68 69 packetLossPercent float64 70 avgRoundTripTime float64 // us 71 minRoundTripTime float64 // us 72 maxRoundTripTime float64 // us 73 stdRoundTripTime float64 // us 74 originBytes []byte 75 reqError string 76 sentPackets int 77 recvPackets int 78 timeout time.Duration 79 ticker *time.Ticker 80 traceroute []*Route 81 } 82 83 func (t *ICMPTask) InitDebug() error { 84 return t.init(true) 85 } 86 87 func (t *ICMPTask) Init() error { 88 return t.init(false) 89 } 90 91 func (t *ICMPTask) init(debug bool) error { 92 if len(t.Timeout) == 0 { 93 t.timeout = PingTimeout 94 } else { 95 if timeout, err := time.ParseDuration(t.Timeout); err != nil { 96 return err 97 } else { 98 t.timeout = timeout 99 } 100 } 101 102 if !debug { 103 du, err := time.ParseDuration(t.Frequency) 104 if err != nil { 105 return err 106 } 107 if t.ticker != nil { 108 t.ticker.Stop() 109 } 110 t.ticker = time.NewTicker(du) 111 } 112 113 if strings.EqualFold(t.CurStatus, StatusStop) { 114 return nil 115 } 116 117 if len(t.SuccessWhen) == 0 { 118 return fmt.Errorf(`no any check rule`) 119 } 120 121 if t.PacketCount <= 0 { 122 t.PacketCount = 3 123 } 124 125 for _, checker := range t.SuccessWhen { 126 if checker.ResponseTime != nil { 127 for _, resp := range checker.ResponseTime { 128 du, err := time.ParseDuration(resp.Target) 129 if err != nil { 130 return err 131 } 132 resp.target = float64(du.Microseconds()) // us 133 } 134 } 135 136 // if [checker.Hops] is not nil, set traceroute to be true 137 if checker.Hops != nil { 138 t.EnableTraceroute = true 139 } 140 } 141 142 t.originBytes = make([]byte, 2000) 143 144 return nil 145 } 146 147 func (t *ICMPTask) Check() error { 148 if t.ExternalID == "" { 149 return fmt.Errorf("external ID missing") 150 } 151 152 if len(t.Host) == 0 { 153 return fmt.Errorf("host should not be empty") 154 } 155 156 return t.Init() 157 } 158 159 func (t *ICMPTask) CheckResult() (reasons []string, succFlag bool) { 160 for _, chk := range t.SuccessWhen { 161 // check response time 162 for _, v := range chk.ResponseTime { 163 vs := &ValueSuccess{ 164 Op: v.Op, 165 Target: v.target, 166 } 167 168 checkVal := float64(0) 169 170 switch v.Func { 171 case "avg": 172 checkVal = t.avgRoundTripTime 173 case "min": 174 checkVal = t.minRoundTripTime 175 case "max": 176 checkVal = t.maxRoundTripTime 177 case "std": 178 checkVal = t.stdRoundTripTime 179 } 180 181 if t.packetLossPercent == 100 { 182 reasons = append(reasons, "all packets lost") 183 } else if err := vs.check(checkVal); err != nil { 184 reasons = append(reasons, 185 fmt.Sprintf("ICMP round-trip(%s) check failed: %s", v.Func, err.Error())) 186 } else { 187 succFlag = true 188 } 189 } 190 191 // check packet loss 192 for _, v := range chk.PacketLossPercent { 193 if err := v.check(t.packetLossPercent); err != nil { 194 reasons = append(reasons, fmt.Sprintf("packet_loss_percent check failed: %s", err.Error())) 195 } else { 196 succFlag = true 197 } 198 } 199 200 // check packets received 201 for _, v := range chk.Packets { 202 if err := v.check(float64(t.recvPackets)); err != nil { 203 reasons = append(reasons, fmt.Sprintf("packets received check failed: %s", err.Error())) 204 } else { 205 succFlag = true 206 } 207 } 208 209 // check traceroute 210 if t.EnableTraceroute { 211 hops := float64(len(t.traceroute)) 212 if hops == 0 { 213 reasons = append(reasons, "traceroute failed with no hops") 214 } else { 215 for _, v := range chk.Hops { 216 if err := v.check(hops); err != nil { 217 reasons = append(reasons, fmt.Sprintf("traceroute hops check failed: %s", err.Error())) 218 } else { 219 succFlag = true 220 } 221 } 222 } 223 } 224 } 225 226 return reasons, succFlag 227 } 228 229 func (t *ICMPTask) GetResults() (tags map[string]string, fields map[string]interface{}) { 230 tags = map[string]string{ 231 "name": t.Name, 232 "dest_host": t.Host, 233 "status": "FAIL", 234 "proto": "icmp", 235 } 236 237 fields = map[string]interface{}{ 238 "average_round_trip_time_in_millis": t.round(t.avgRoundTripTime/1000, 3), 239 "average_round_trip_time": t.avgRoundTripTime, 240 "min_round_trip_time_in_millis": t.round(t.minRoundTripTime/1000, 3), 241 "min_round_trip_time": t.minRoundTripTime, 242 "std_round_trip_time_in_millis": t.round(t.stdRoundTripTime/1000, 3), 243 "std_round_trip_time": t.stdRoundTripTime, 244 "max_round_trip_time_in_millis": t.round(t.maxRoundTripTime/1000, 3), 245 "max_round_trip_time": t.maxRoundTripTime, 246 "packet_loss_percent": t.packetLossPercent, 247 "packets_sent": t.sentPackets, 248 "packets_received": t.recvPackets, 249 "success": int64(-1), 250 } 251 252 for k, v := range t.Tags { 253 tags[k] = v 254 } 255 256 if t.EnableTraceroute { 257 fields["hops"] = 0 258 if t.traceroute == nil { 259 fields["traceroute"] = "[]" 260 } else { 261 tracerouteData, err := json.Marshal(t.traceroute) 262 if err == nil && len(tracerouteData) > 0 { 263 fields["traceroute"] = string(tracerouteData) 264 fields["hops"] = len(t.traceroute) 265 } else { 266 fields["traceroute"] = "[]" 267 } 268 } 269 } 270 271 message := map[string]interface{}{} 272 273 reasons, succFlag := t.CheckResult() 274 if t.reqError != "" { 275 reasons = append(reasons, t.reqError) 276 } 277 278 switch t.SuccessWhenLogic { 279 case "or": 280 if succFlag && t.reqError == "" { 281 tags["status"] = "OK" 282 fields["success"] = int64(1) 283 message["average_round_trip_time"] = t.avgRoundTripTime 284 } else { 285 message[`fail_reason`] = strings.Join(reasons, `;`) 286 fields[`fail_reason`] = strings.Join(reasons, `;`) 287 } 288 default: 289 if len(reasons) != 0 { 290 message[`fail_reason`] = strings.Join(reasons, `;`) 291 fields[`fail_reason`] = strings.Join(reasons, `;`) 292 } else { 293 message["average_round_trip_time"] = t.avgRoundTripTime 294 } 295 296 if t.reqError == "" && len(reasons) == 0 { 297 tags["status"] = "OK" 298 fields["success"] = int64(1) 299 } 300 } 301 302 data, err := json.Marshal(message) 303 if err != nil { 304 fields[`message`] = err.Error() 305 } 306 307 if len(data) > MaxMsgSize { 308 fields[`message`] = string(data[:MaxMsgSize]) 309 } else { 310 fields[`message`] = string(data) 311 } 312 313 return tags, fields 314 } 315 316 func (t *ICMPTask) MetricName() string { 317 return `icmp_dial_testing` 318 } 319 320 func (t *ICMPTask) Clear() { 321 if t.timeout == 0 { 322 t.timeout = PingTimeout 323 } 324 325 t.avgRoundTripTime = 0 326 t.minRoundTripTime = 0 327 t.maxRoundTripTime = 0 328 t.stdRoundTripTime = 0 329 330 t.recvPackets = 0 331 t.sentPackets = 0 332 333 t.packetLossPercent = 100 334 t.reqError = "" 335 t.traceroute = nil 336 } 337 338 func (t *ICMPTask) Run() error { 339 t.Clear() 340 341 pinger, err := ping.NewPinger(t.Host) 342 if err != nil { 343 t.reqError = err.Error() 344 return err 345 } 346 347 if t.PacketCount > 0 { 348 pinger.Count = t.PacketCount 349 } else { 350 pinger.Count = 3 351 } 352 353 pinger.Interval = 1 * time.Second 354 355 pinger.Timeout = time.Duration(pinger.Count) * pinger.Interval 356 357 pinger.SetPrivileged(true) 358 359 if err := pinger.Run(); err != nil { 360 t.reqError = err.Error() 361 } else { 362 stats := pinger.Statistics() 363 364 t.packetLossPercent = stats.PacketLoss 365 t.sentPackets = stats.PacketsSent 366 t.recvPackets = stats.PacketsRecv 367 t.minRoundTripTime = t.round(float64(stats.MinRtt.Nanoseconds())/1e3, 3) 368 t.avgRoundTripTime = t.round(float64(stats.AvgRtt.Nanoseconds())/1e3, 3) 369 t.maxRoundTripTime = t.round(float64(stats.MaxRtt.Nanoseconds())/1e3, 3) 370 t.stdRoundTripTime = t.round(float64(stats.StdDevRtt.Nanoseconds())/1e3, 3) 371 } 372 373 if t.EnableTraceroute { 374 hostIP := net.ParseIP(t.Host) 375 if hostIP == nil { 376 if ips, err := net.LookupIP(t.Host); err != nil { 377 t.reqError = err.Error() 378 return err 379 } else { 380 if len(ips) == 0 { 381 err := fmt.Errorf("invalid host: %s, found no ip record", t.Host) 382 t.reqError = err.Error() 383 return err 384 } else { 385 hostIP = ips[0] 386 } 387 } 388 } 389 routes, err := TracerouteIP(hostIP.String(), t.TracerouteConfig) 390 if err != nil { 391 t.reqError = err.Error() 392 } else { 393 t.traceroute = routes 394 } 395 } 396 397 return nil 398 } 399 400 func (t *ICMPTask) round(num float64, n int) float64 { 401 s := fmt.Sprintf("%."+strconv.Itoa(n)+"f", num) 402 roundNum, _ := strconv.ParseFloat(s, 64) 403 404 return roundNum 405 } 406 407 func (t *ICMPTask) Stop() error { 408 return nil 409 } 410 411 func (t *ICMPTask) UpdateTimeUs() int64 { 412 return t.UpdateTime 413 } 414 415 func (t *ICMPTask) ID() string { 416 if t.ExternalID == `` { 417 return cliutils.XID("dtst_") 418 } 419 return fmt.Sprintf("%s_%s", t.AK, t.ExternalID) 420 } 421 422 func (t *ICMPTask) GetOwnerExternalID() string { 423 return t.OwnerExternalID 424 } 425 426 func (t *ICMPTask) SetOwnerExternalID(exid string) { 427 t.OwnerExternalID = exid 428 } 429 430 func (t *ICMPTask) SetRegionID(regionID string) { 431 t.Region = regionID 432 } 433 434 func (t *ICMPTask) SetAk(ak string) { 435 t.AK = ak 436 } 437 438 func (t *ICMPTask) SetStatus(status string) { 439 t.CurStatus = status 440 } 441 442 func (t *ICMPTask) SetUpdateTime(ts int64) { 443 t.UpdateTime = ts 444 } 445 446 func (t *ICMPTask) Status() string { 447 return t.CurStatus 448 } 449 450 func (t *ICMPTask) Ticker() *time.Ticker { 451 return t.ticker 452 } 453 454 func (t *ICMPTask) Class() string { 455 return ClassICMP 456 } 457 458 func (t *ICMPTask) GetFrequency() string { 459 return t.Frequency 460 } 461 462 func (t *ICMPTask) GetLineData() string { 463 return "" 464 } 465 466 func (t *ICMPTask) RegionName() string { 467 return t.Region 468 } 469 470 func (t *ICMPTask) PostURLStr() string { 471 return t.PostURL 472 } 473 474 func (t *ICMPTask) AccessKey() string { 475 return t.AK 476 } 477 478 func (t *ICMPTask) CheckSum(data []byte) (rt uint16) { 479 var ( 480 sum uint32 481 length int = len(data) 482 index int 483 ) 484 for length > 1 { 485 sum += uint32(data[index])<<8 + uint32(data[index+1]) 486 index += 2 487 length -= 2 488 } 489 if length > 0 { 490 sum += uint32(data[index]) << 8 491 } 492 493 rt = uint16(sum) + uint16(sum>>16) 494 495 return ^rt 496 } 497 498 func (t *ICMPTask) GetHostName() (string, error) { 499 return t.Host, nil 500 } 501 502 func (t *ICMPTask) GetWorkspaceLanguage() string { 503 if t.WorkspaceLanguage == "en" { 504 return "en" 505 } 506 return "zh" 507 } 508 509 func (t *ICMPTask) GetTagsInfo() string { 510 return t.TagsInfo 511 }