package checker import ( "context" "fmt" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) func Rule() sdk.CheckRule { return &sipRule{} } type sipRule struct{} func (r *sipRule) Name() string { return "sip_server" } func (r *sipRule) Description() string { return "Checks DNS resolution, reachability and OPTIONS response of a SIP/VoIP server" } func (r *sipRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState { var data SIPData if err := obs.Get(ctx, ObservationKeySIP, &data); err != nil { return sdk.CheckState{ Status: sdk.StatusError, Message: fmt.Sprintf("failed to load SIP observation: %v", err), Code: "sip.observation_error", } } issues := append([]Issue(nil), data.Issues...) // Fold in related TLS observations so cert issues surface on the // SIP page without requiring a separate glance at the TLS report. related, _ := obs.GetRelated(ctx, TLSRelatedKey) issues = append(issues, tlsIssuesFromRelated(related)...) worst := sdk.StatusOK critMsgs, warnMsgs := []string{}, []string{} var firstCritCode, firstWarnCode string for _, is := range issues { switch is.Severity { case SeverityCrit: if worst < sdk.StatusCrit { worst = sdk.StatusCrit } if firstCritCode == "" { firstCritCode = is.Code } critMsgs = append(critMsgs, is.Message) case SeverityWarn: if worst < sdk.StatusWarn { worst = sdk.StatusWarn } if firstWarnCode == "" { firstWarnCode = is.Code } warnMsgs = append(warnMsgs, is.Message) } } transports := []string{} if data.Coverage.WorkingUDP { transports = append(transports, "udp") } if data.Coverage.WorkingTCP { transports = append(transports, "tcp") } if data.Coverage.WorkingTLS { transports = append(transports, "tls") } meta := map[string]any{ "working_udp": data.Coverage.WorkingUDP, "working_tcp": data.Coverage.WorkingTCP, "working_tls": data.Coverage.WorkingTLS, "has_ipv4": data.Coverage.HasIPv4, "has_ipv6": data.Coverage.HasIPv6, "endpoints": len(data.Endpoints), "issue_count": len(data.Issues), } switch worst { case sdk.StatusOK: return sdk.CheckState{ Status: sdk.StatusOK, Message: fmt.Sprintf("SIP operational (%s, %d endpoints)", strings.Join(transports, "+"), len(data.Endpoints)), Code: "sip.ok", Meta: meta, } case sdk.StatusWarn: return sdk.CheckState{ Status: sdk.StatusWarn, Message: "SIP works with warnings: " + joinTop(warnMsgs, 2), Code: firstWarnCode, Meta: meta, } default: return sdk.CheckState{ Status: sdk.StatusCrit, Message: "SIP broken: " + joinTop(critMsgs, 2), Code: firstCritCode, Meta: meta, } } } func joinTop(msgs []string, n int) string { if len(msgs) == 0 { return "" } if len(msgs) <= n { return strings.Join(msgs, "; ") } return strings.Join(msgs[:n], "; ") + fmt.Sprintf(" (+%d more)", len(msgs)-n) }