checker-sip/checker/types.go

163 lines
5.9 KiB
Go

// Package checker implements the SIP / VoIP server checker for
// happyDomain.
//
// It probes a domain's SIP deployment end-to-end (NAPTR + SRV
// resolution per RFC 3263, reachability on UDP / TCP / TLS, SIP
// OPTIONS ping per RFC 3261) and reports actionable findings.
//
// TLS certificate chain / SAN / expiry / cipher posture is
// intentionally out of scope, the forthcoming checker-tls covers
// that. SIPS endpoints are published as "tls" discovery endpoints
// so checker-tls can probe them; its findings are folded back into
// this report via GetRelated("tls_probes"). See
// happydomain3/docs/checker-discovery-endpoint.md.
package checker
import (
sdk "git.happydns.org/checker-sdk-go/checker"
)
const ObservationKeySIP sdk.ObservationKey = "sip"
// Transport identifies one of the three SIP transports we probe.
type Transport string
const (
TransportUDP Transport = "udp"
TransportTCP Transport = "tcp"
TransportTLS Transport = "tls" // SIPS, direct TLS on connect
)
// SIPData is the full observation stored per run. It is a pure record of
// what was observed, no severity or pass/fail judgment is encoded here;
// those are derived by the rules (see issues.go / rules_*.go).
type SIPData struct {
Domain string `json:"domain"`
RunAt string `json:"run_at"`
NAPTR []NAPTRRecord `json:"naptr,omitempty"`
SRV SRVLookup `json:"srv"`
Endpoints []EndpointProbe `json:"endpoints"`
}
// NAPTRRecord is a subset of a NAPTR record enough to reason about
// SIP service resolution.
type NAPTRRecord struct {
Service string `json:"service"` // e.g. "SIP+D2T"
Regexp string `json:"regexp,omitempty"`
Replacement string `json:"replacement,omitempty"`
Flags string `json:"flags,omitempty"`
Order uint16 `json:"order"`
Preference uint16 `json:"preference"`
}
// SRVLookup groups the SRV records found per transport plus per-prefix
// lookup errors and a fallback marker when no SRV was published.
type SRVLookup struct {
UDP []SRVRecord `json:"udp,omitempty"`
TCP []SRVRecord `json:"tcp,omitempty"`
SIPS []SRVRecord `json:"sips,omitempty"`
// Errors per-set, keyed by SRV prefix ("_sip._udp.", …).
Errors map[string]string `json:"errors,omitempty"`
// FallbackProbed is true when no SRV was published and we probed
// the bare domain on 5060 / 5061.
FallbackProbed bool `json:"fallback_probed,omitempty"`
}
// SRVRecord captures one SRV plus the addresses it resolves to.
type SRVRecord struct {
Target string `json:"target"`
Port uint16 `json:"port"`
Priority uint16 `json:"priority"`
Weight uint16 `json:"weight"`
IPv4 []string `json:"ipv4,omitempty"`
IPv6 []string `json:"ipv6,omitempty"`
}
// EndpointProbe is the result of probing one (transport, target, address).
type EndpointProbe struct {
Transport Transport `json:"transport"`
SRVPrefix string `json:"srv_prefix"`
Target string `json:"target"`
Port uint16 `json:"port"`
Address string `json:"address"`
IsIPv6 bool `json:"is_ipv6,omitempty"`
Reachable bool `json:"reachable"`
ReachableErr string `json:"reachable_err,omitempty"`
TLSVersion string `json:"tls_version,omitempty"`
TLSCipher string `json:"tls_cipher,omitempty"`
OptionsSent bool `json:"options_sent,omitempty"`
OptionsStatus string `json:"options_status,omitempty"` // e.g. "200 OK"
OptionsRawCode int `json:"options_raw_code,omitempty"`
OptionsRTTMs int64 `json:"options_rtt_ms,omitempty"`
ServerHeader string `json:"server_header,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
AllowMethods []string `json:"allow_methods,omitempty"`
ContactURI string `json:"contact_uri,omitempty"`
ElapsedMS int64 `json:"elapsed_ms"`
Error string `json:"error,omitempty"`
}
// OK reports whether this probe counts as a working SIP endpoint
// (reachable + 2xx answer to OPTIONS).
func (e EndpointProbe) OK() bool {
return e.Reachable && e.OptionsSent && e.OptionsRawCode >= 200 && e.OptionsRawCode < 300
}
// Coverage is a roll-up of the per-endpoint results. All fields reflect
// what was *reachable* during this run, not what was merely published in
// DNS: HasIPv6 is true only if at least one AAAA-resolved endpoint
// accepted a connection. A target with AAAA but firewalled off will not
// light up HasIPv6.
type Coverage struct {
HasIPv4 bool `json:"has_ipv4"`
HasIPv6 bool `json:"has_ipv6"`
WorkingUDP bool `json:"working_udp"`
WorkingTCP bool `json:"working_tcp"`
WorkingTLS bool `json:"working_tls"`
AnyWorking bool `json:"any_working"`
}
// Issue is a structured finding. The rule reduces issues to a worst
// severity; the report renders them as an actionable fix list.
type Issue struct {
Code string `json:"code"`
Severity string `json:"severity"` // "info" | "warn" | "crit"
Message string `json:"message"`
Fix string `json:"fix,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
}
// Severities. Match checker-xmpp conventions for cross-checker
// consistency.
const (
SeverityInfo = "info"
SeverityWarn = "warn"
SeverityCrit = "crit"
)
// Issue codes. Keep short, stable, prefixed with "sip." so downstream
// consumers can filter.
const (
CodeNoSRV = "sip.no_srv"
CodeOnlyUDP = "sip.srv.only_udp"
CodeNoTLS = "sip.srv.no_tls"
CodeSRVServfail = "sip.srv.servfail"
CodeSRVTargetUnresolved = "sip.srv.target_unresolvable"
CodeNAPTRServfail = "sip.naptr.servfail"
CodeTCPUnreachable = "sip.tcp.unreachable"
CodeUDPUnreachable = "sip.udp.unreachable"
CodeTLSHandshake = "sip.tls.handshake_failed"
CodeOptionsNoAnswer = "sip.options.no_response"
CodeOptionsNon2xx = "sip.options.non_2xx"
CodeOptionsNoAllow = "sip.options.no_allow"
CodeOptionsNoInvite = "sip.options.no_invite"
CodeFallbackProbed = "sip.fallback_probed"
CodeNoIPv6 = "sip.no_ipv6"
CodeAllDown = "sip.all_endpoints_down"
)