package monitor import "context" import "fmt" import "net" import "strings" import "time" import "codit/internal/models" // runCheck dispatches a monitor to its checker by family and returns the result. // productID (the normalized server id) composes the probe's User-Agent / ICMP // payload so no product name is hardcoded. func runCheck(ctx context.Context, policy EgressPolicy, m models.Monitor, productID string, tlsCtx *TLSContext) models.MonitorCheckResult { switch m.Type { case models.MonitorTypeURL: return checkURL(ctx, policy, m, productID, tlsCtx) case models.MonitorTypeDNS: return checkDNS(ctx, m) case models.MonitorTypePing: return checkPing(ctx, policy, m, productID) default: return models.MonitorCheckResult{OK: false, State: models.MonitorStateDown, Message: fmt.Sprintf("check type %q not implemented", m.Type)} } } // hostAndPort splits a target into host and port, stripping any scheme/path and // applying defaultPort when no port is present. func hostAndPort(target string, defaultPort string) (string, string) { var host string var port string var i int var err error target = strings.TrimSpace(target) if i = strings.Index(target, "://"); i >= 0 { target = target[i+3:] } if i = strings.IndexByte(target, '/'); i >= 0 { target = target[:i] } host, port, err = net.SplitHostPort(target) if err != nil { return target, defaultPort } return host, port } // classifyDialError maps a connection error to a concise message, surfacing the // egress-block and timeout cases. func classifyDialError(err error) string { var message string message = err.Error() if strings.Contains(message, ErrBlockedEgress.Error()) { return "blocked by egress policy (target resolves to an internal address)" } if strings.Contains(message, context.DeadlineExceeded.Error()) || strings.Contains(message, "timeout") || strings.Contains(message, "Timeout") { return "timed out" } return message } func checkTimeout(m models.Monitor) time.Duration { var d time.Duration d = time.Duration(m.TimeoutSec) * time.Second if d <= 0 { d = 10 * time.Second } return d } func failResult(message string) models.MonitorCheckResult { return models.MonitorCheckResult{OK: false, State: models.MonitorStateDown, Message: message} } // worseState returns the more severe of two monitor states (down > degraded > // up > pending), used to aggregate facet states into the monitor state. func worseState(a string, b string) string { if stateRank(b) > stateRank(a) { return b } return a } func stateRank(s string) int { switch s { case models.MonitorStateDown: return 3 case models.MonitorStateDegraded: return 2 case models.MonitorStateUp: return 1 default: return 0 } }