github.com/GuanceCloud/cliutils@v1.1.21/dialtesting/tcp.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 "context" 10 "encoding/json" 11 "fmt" 12 "net" 13 "strings" 14 "time" 15 16 "github.com/GuanceCloud/cliutils" 17 ) 18 19 const defaultTCPTimeout = 30 * time.Second 20 21 type TCPResponseTime struct { 22 IsContainDNS bool `json:"is_contain_dns"` 23 Target string `json:"target"` 24 25 targetTime time.Duration 26 } 27 28 type TCPSuccess struct { 29 ResponseTime []*TCPResponseTime `json:"response_time,omitempty"` 30 Hops []*ValueSuccess `json:"hops,omitempty"` 31 ResponseMessage []*SuccessOption `json:"response_message,omitempty"` 32 } 33 34 type TCPTask struct { 35 Host string `json:"host"` 36 Port string `json:"port"` 37 Message string `json:"message"` 38 Timeout string `json:"timeout"` 39 EnableTraceroute bool `json:"enable_traceroute"` 40 TracerouteConfig *TracerouteOption `json:"traceroute_config"` 41 SuccessWhen []*TCPSuccess `json:"success_when"` 42 SuccessWhenLogic string `json:"success_when_logic"` 43 ExternalID string `json:"external_id"` 44 Name string `json:"name"` 45 AK string `json:"access_key"` 46 PostURL string `json:"post_url"` 47 CurStatus string `json:"status"` 48 Frequency string `json:"frequency"` 49 Region string `json:"region"` 50 OwnerExternalID string `json:"owner_external_id"` 51 Tags map[string]string `json:"tags,omitempty"` 52 Labels []string `json:"labels,omitempty"` 53 UpdateTime int64 `json:"update_time,omitempty"` 54 WorkspaceLanguage string `json:"workspace_language,omitempty"` 55 TagsInfo string `json:"tags_info,omitempty"` 56 57 reqCost time.Duration 58 reqDNSCost time.Duration 59 reqError string 60 destIP string 61 responseMessage string 62 timeout time.Duration 63 ticker *time.Ticker 64 traceroute []*Route 65 } 66 67 func (t *TCPTask) InitDebug() error { 68 return t.init(true) 69 } 70 71 func (t *TCPTask) init(debug bool) error { 72 if len(t.Timeout) == 0 { 73 t.timeout = 10 * time.Second 74 } else { 75 if timeout, err := time.ParseDuration(t.Timeout); err != nil { 76 return err 77 } else { 78 t.timeout = timeout 79 } 80 } 81 82 if !debug { 83 du, err := time.ParseDuration(t.Frequency) 84 if err != nil { 85 return err 86 } 87 if t.ticker != nil { 88 t.ticker.Stop() 89 } 90 t.ticker = time.NewTicker(du) 91 } 92 93 if strings.EqualFold(t.CurStatus, StatusStop) { 94 return nil 95 } 96 97 if len(t.SuccessWhen) == 0 { 98 return fmt.Errorf(`no any check rule`) 99 } 100 101 for _, checker := range t.SuccessWhen { 102 if checker.ResponseTime != nil { 103 for _, v := range checker.ResponseTime { 104 du, err := time.ParseDuration(v.Target) 105 if err != nil { 106 return err 107 } 108 v.targetTime = du 109 } 110 } 111 112 // if [checker.Hops] is not nil, set traceroute to be true 113 if checker.Hops != nil { 114 t.EnableTraceroute = true 115 } 116 117 for _, v := range checker.ResponseMessage { 118 err := genReg(v) 119 if err != nil { 120 return err 121 } 122 } 123 } 124 125 return nil 126 } 127 128 func (t *TCPTask) Init() error { 129 return t.init(false) 130 } 131 132 func (t *TCPTask) Check() error { 133 if t.ExternalID == "" { 134 return fmt.Errorf("external ID missing") 135 } 136 137 if len(t.Host) == 0 { 138 return fmt.Errorf("host should not be empty") 139 } 140 141 if len(t.Port) == 0 { 142 return fmt.Errorf("port should not be empty") 143 } 144 145 return t.Init() 146 } 147 148 func (t *TCPTask) CheckResult() (reasons []string, succFlag bool) { 149 for _, chk := range t.SuccessWhen { 150 // check response time 151 if chk.ResponseTime != nil { 152 for _, v := range chk.ResponseTime { 153 reqCost := t.reqCost 154 155 if v.IsContainDNS { 156 reqCost += t.reqDNSCost 157 } 158 159 if reqCost >= v.targetTime { 160 reasons = append(reasons, 161 fmt.Sprintf("TCP response time(%v) larger equal than %v", reqCost, v.targetTime)) 162 } else if v.targetTime > 0 { 163 succFlag = true 164 } 165 } 166 } 167 168 // check message 169 if chk.ResponseMessage != nil { 170 for _, v := range chk.ResponseMessage { 171 if err := v.check(t.responseMessage, "response message"); err != nil { 172 reasons = append(reasons, err.Error()) 173 } else { 174 succFlag = true 175 } 176 } 177 } 178 179 // check traceroute 180 if t.EnableTraceroute { 181 hops := float64(len(t.traceroute)) 182 183 if hops == 0 { 184 reasons = append(reasons, "traceroute failed with no hops") 185 } else { 186 for _, v := range chk.Hops { 187 if err := v.check(hops); err != nil { 188 reasons = append(reasons, fmt.Sprintf("traceroute hops check failed: %s", err.Error())) 189 } else { 190 succFlag = true 191 } 192 } 193 } 194 } 195 } 196 197 return reasons, succFlag 198 } 199 200 func (t *TCPTask) GetResults() (tags map[string]string, fields map[string]interface{}) { 201 tags = map[string]string{ 202 "name": t.Name, 203 "dest_host": t.Host, 204 "dest_port": t.Port, 205 "dest_ip": t.destIP, 206 "status": "FAIL", 207 "proto": "tcp", 208 } 209 210 responseTime := int64(t.reqCost) / 1000 // us 211 responseTimeWithDNS := int64(t.reqCost+t.reqDNSCost) / 1000 // us 212 213 fields = map[string]interface{}{ 214 "response_time": responseTime, 215 "response_time_with_dns": responseTimeWithDNS, 216 "success": int64(-1), 217 } 218 219 if t.responseMessage != "" { 220 fields["response_message"] = t.responseMessage 221 } 222 223 if t.EnableTraceroute { 224 fields["hops"] = 0 225 if t.traceroute == nil { 226 fields["traceroute"] = "[]" 227 } else { 228 tracerouteData, err := json.Marshal(t.traceroute) 229 if err == nil && len(tracerouteData) > 0 { 230 fields["traceroute"] = string(tracerouteData) 231 fields["hops"] = len(t.traceroute) 232 } else { 233 fields["traceroute"] = "[]" 234 } 235 } 236 } 237 238 for k, v := range t.Tags { 239 tags[k] = v 240 } 241 242 message := map[string]interface{}{} 243 244 reasons, succFlag := t.CheckResult() 245 if t.reqError != "" { 246 reasons = append(reasons, t.reqError) 247 } 248 249 switch t.SuccessWhenLogic { 250 case "or": 251 if succFlag && t.reqError == "" { 252 tags["status"] = "OK" 253 fields["success"] = int64(1) 254 message["response_time"] = responseTime 255 } else { 256 message[`fail_reason`] = strings.Join(reasons, `;`) 257 fields[`fail_reason`] = strings.Join(reasons, `;`) 258 } 259 default: 260 if len(reasons) != 0 { 261 message[`fail_reason`] = strings.Join(reasons, `;`) 262 fields[`fail_reason`] = strings.Join(reasons, `;`) 263 } else { 264 message["response_time"] = responseTime 265 } 266 267 if t.reqError == "" && len(reasons) == 0 { 268 tags["status"] = "OK" 269 fields["success"] = int64(1) 270 } 271 } 272 273 data, err := json.Marshal(message) 274 if err != nil { 275 fields[`message`] = err.Error() 276 } 277 278 if len(data) > MaxMsgSize { 279 fields[`message`] = string(data[:MaxMsgSize]) 280 } else { 281 fields[`message`] = string(data) 282 } 283 284 return tags, fields 285 } 286 287 func (t *TCPTask) MetricName() string { 288 return `tcp_dial_testing` 289 } 290 291 func (t *TCPTask) Clear() { 292 t.reqCost = 0 293 t.reqError = "" 294 t.traceroute = nil 295 } 296 297 func (t *TCPTask) Run() error { 298 t.Clear() 299 300 var d net.Dialer 301 ctx, cancel := context.WithTimeout(context.Background(), t.timeout) 302 defer cancel() 303 304 hostIP := net.ParseIP(t.Host) 305 306 if hostIP == nil { // host name 307 start := time.Now() 308 if ips, err := net.LookupIP(t.Host); err != nil { 309 t.reqError = err.Error() 310 return err 311 } else { 312 if len(ips) == 0 { 313 err := fmt.Errorf("invalid host: %s, found no ip record", t.Host) 314 t.reqError = err.Error() 315 return err 316 } else { 317 t.reqDNSCost = time.Since(start) 318 hostIP = ips[0] // TODO: support mutiple ip for one host 319 } 320 } 321 } 322 323 t.destIP = hostIP.String() 324 tcpIPAddr := net.JoinHostPort(hostIP.String(), t.Port) 325 326 start := time.Now() 327 conn, err := d.DialContext(ctx, "tcp", tcpIPAddr) 328 329 if err != nil { 330 t.reqError = err.Error() 331 t.reqDNSCost = 0 332 } else { 333 t.reqCost = time.Since(start) 334 if t.Message != "" { // send message and get response 335 if err := conn.SetDeadline(time.Now().Add(defaultTCPTimeout)); err != nil { 336 t.reqError = err.Error() 337 } else if _, err := conn.Write([]byte(t.Message)); err != nil { 338 t.reqError = err.Error() 339 } else { 340 buf := make([]byte, 1024) 341 if n, err := conn.Read(buf); err != nil { 342 t.reqError = err.Error() 343 } else { 344 t.responseMessage = string(buf[:n]) 345 } 346 } 347 } 348 349 _ = conn.Close() //nolint:errcheck 350 } 351 352 if t.EnableTraceroute { 353 routes, err := TracerouteIP(hostIP.String(), t.TracerouteConfig) 354 if err != nil { 355 t.reqError = err.Error() 356 } else { 357 t.traceroute = routes 358 } 359 } 360 361 return nil 362 } 363 364 func (t *TCPTask) Stop() error { 365 return nil 366 } 367 368 func (t *TCPTask) UpdateTimeUs() int64 { 369 return t.UpdateTime 370 } 371 372 func (t *TCPTask) ID() string { 373 if t.ExternalID == `` { 374 return cliutils.XID("dtst_") 375 } 376 return fmt.Sprintf("%s_%s", t.AK, t.ExternalID) 377 } 378 379 func (t *TCPTask) GetOwnerExternalID() string { 380 return t.OwnerExternalID 381 } 382 383 func (t *TCPTask) SetOwnerExternalID(exid string) { 384 t.OwnerExternalID = exid 385 } 386 387 func (t *TCPTask) SetRegionID(regionID string) { 388 t.Region = regionID 389 } 390 391 func (t *TCPTask) SetAk(ak string) { 392 t.AK = ak 393 } 394 395 func (t *TCPTask) SetStatus(status string) { 396 t.CurStatus = status 397 } 398 399 func (t *TCPTask) SetUpdateTime(ts int64) { 400 t.UpdateTime = ts 401 } 402 403 func (t *TCPTask) Status() string { 404 return t.CurStatus 405 } 406 407 func (t *TCPTask) Ticker() *time.Ticker { 408 return t.ticker 409 } 410 411 func (t *TCPTask) Class() string { 412 return ClassTCP 413 } 414 415 func (t *TCPTask) GetFrequency() string { 416 return t.Frequency 417 } 418 419 func (t *TCPTask) GetLineData() string { 420 return "" 421 } 422 423 func (t *TCPTask) RegionName() string { 424 return t.Region 425 } 426 427 func (t *TCPTask) PostURLStr() string { 428 return t.PostURL 429 } 430 431 func (t *TCPTask) AccessKey() string { 432 return t.AK 433 } 434 435 func (t *TCPTask) GetHostName() (string, error) { 436 return t.Host, nil 437 } 438 439 func (t *TCPTask) GetWorkspaceLanguage() string { 440 if t.WorkspaceLanguage == "en" { 441 return "en" 442 } 443 return "zh" 444 } 445 446 func (t *TCPTask) GetTagsInfo() string { 447 return t.TagsInfo 448 }