// Package checker implements the STUN/TURN checker for happyDomain. // // The checker drives a target server through the STUN binding and TURN // allocation/relay protocols (RFC 5389, RFC 5766) using the Pion libraries, // then exposes a structured observation and a rich HTML report including // remediation guidance for the most common deployment mistakes. package checker import ( "time" sdk "git.happydns.org/checker-sdk-go/checker" ) // ObservationKeyStunTurn is the observation key for STUN/TURN test data. const ObservationKeyStunTurn sdk.ObservationKey = "stun_turn" // Transport identifies the L4/L4-secure transport used to reach an endpoint. type Transport string const ( TransportUDP Transport = "udp" TransportTCP Transport = "tcp" TransportTLS Transport = "tls" TransportDTLS Transport = "dtls" ) // Endpoint is a single resolved server target to probe. type Endpoint struct { URI string `json:"uri"` Host string `json:"host"` Port uint16 `json:"port"` Transport Transport `json:"transport"` Secure bool `json:"secure"` IsTURN bool `json:"is_turn"` // false: STUN-only scheme, true: TURN scheme Source string `json:"source"` // "uri" or "srv:_turn._udp.example.com" } // SubTestStatus mirrors sdk.Status as a string for JSON friendliness. type SubTestStatus string const ( SubTestOK SubTestStatus = "ok" SubTestInfo SubTestStatus = "info" SubTestWarn SubTestStatus = "warn" SubTestCrit SubTestStatus = "crit" SubTestSkipped SubTestStatus = "skipped" SubTestError SubTestStatus = "error" ) // SubTest is one fine-grained test executed against an endpoint. type SubTest struct { Name string `json:"name"` Status SubTestStatus `json:"status"` DurationMs int64 `json:"duration_ms"` Detail string `json:"detail,omitempty"` Error string `json:"error,omitempty"` Fix string `json:"fix,omitempty"` } // EndpointReport gathers all sub-tests run against a single endpoint. type EndpointReport struct { Endpoint Endpoint `json:"endpoint"` SubTests []SubTest `json:"sub_tests"` } // Worst returns the worst sub-test status of the endpoint. func (e EndpointReport) Worst() SubTestStatus { worst := SubTestOK for _, t := range e.SubTests { if statusRank(t.Status) > statusRank(worst) { worst = t.Status } } return worst } // FirstFailure returns the first non-OK/Info/Skipped sub-test. func (e EndpointReport) FirstFailure() *SubTest { for i := range e.SubTests { s := e.SubTests[i].Status if s != SubTestOK && s != SubTestInfo && s != SubTestSkipped { return &e.SubTests[i] } } return nil } // StunTurnData is the JSON-serializable observation payload. type StunTurnData struct { Zone string `json:"zone,omitempty"` Mode string `json:"mode,omitempty"` CollectedAt time.Time `json:"collected_at"` Endpoints []EndpointReport `json:"endpoints"` GlobalError string `json:"global_error,omitempty"` } // statusRank converts a SubTestStatus to an orderable severity. func statusRank(s SubTestStatus) int { switch s { case SubTestOK: return 0 case SubTestSkipped: return 1 case SubTestInfo: return 2 case SubTestWarn: return 3 case SubTestCrit: return 4 case SubTestError: return 5 } return -1 } // toSDKStatus converts the worst SubTestStatus seen to an sdk.Status. func toSDKStatus(s SubTestStatus) sdk.Status { switch s { case SubTestOK: return sdk.StatusOK case SubTestInfo, SubTestSkipped: return sdk.StatusInfo case SubTestWarn: return sdk.StatusWarn case SubTestCrit: return sdk.StatusCrit case SubTestError: return sdk.StatusError } return sdk.StatusUnknown }