// Package ctcp provides TCP connect / port check functionality for RSMon. // // Semantics: the check opens a TCP connection to host:port using the // dialer's timeout, and reports OK as soon as the kernel-level // handshake completes (no banner read, no payload sent). Any dial // failure (refused, timed out, network unreachable, no route, ...) // is reported as ERR with the underlying error message so operators // can distinguish configuration problems from real outages. // // Settings consumed from models.CheckSettings: // - port (string): TCP port to dial. Defaults to 80 when empty. // - timeout (int, seconds): per-dial budget. Clamped to at least 1s. // - host (string): optional override of the monitor host. package ctcp import ( "fmt" "net" "time" "rocketgit.ru/rsmon/worker/app/models" ) const ( stateOK = "OK" stateERR = "ERR" defaultTimeout = 5 * time.Second minTimeout = 1 * time.Second ) // Perform executes a single TCP connect check. func Perform(c *models.Check) *Result { r := &Result{} settings := c.GetSettings() host := c.Monitor.Host if settings.Host != "" { host = settings.Host } if host == "" { r.State = stateERR r.Error = fmt.Errorf("tcp: empty host") return r } port := settings.Port if port == "" { port = "80" } timeout := time.Duration(settings.Timeout) * time.Second if timeout <= 0 { timeout = defaultTimeout } if timeout < minTimeout { timeout = minTimeout } addr := net.JoinHostPort(host, port) dialer := &net.Dialer{Timeout: timeout, DualStack: true} start := time.Now() conn, err := dialer.Dial("tcp", addr) r.Duration = time.Since(start) if err != nil { r.State = stateERR r.Error = err return r } r.RemoteAddr = conn.RemoteAddr().String() if closeErr := conn.Close(); closeErr != nil { r.Warnings = append(r.Warnings, fmt.Sprintf("close: %v", closeErr)) } r.State = stateOK r.Infos = append(r.Infos, fmt.Sprintf("tcp %s in %.2fms", addr, float64(r.Duration.Microseconds())/1000.0)) return r }