// This file is part of the happyDomain (R) project. // Copyright (c) 2020-2026 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. // For commercial licensing, contact us at . // // For AGPL licensing: // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package checker import ( "context" "fmt" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) // Single-priority with multiple targets is Info (not Warn) because it is legal; all-zero-weight within a group is Warn because it almost always indicates a misconfiguration. type rulePriorityWeightSanity struct{} func RulePriorityWeightSanity() sdk.CheckRule { return &rulePriorityWeightSanity{} } func (rulePriorityWeightSanity) Name() string { return "srv_priority_weight_sanity" } func (rulePriorityWeightSanity) Description() string { return "Priority/weight values follow RFC 2782 conventions (failover present, weights meaningful)." } func (rulePriorityWeightSanity) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { d, cs := getData(ctx, obs) if cs != nil { return []sdk.CheckState{*cs} } type groupKey struct{ owner string } type group struct { priorities map[uint16]int zeroWeight map[uint16]int } groups := map[groupKey]*group{} for _, r := range d.Records { if r.IsNullTarget { continue } k := groupKey{owner: r.Owner} g := groups[k] if g == nil { g = &group{priorities: map[uint16]int{}, zeroWeight: map[uint16]int{}} groups[k] = g } g.priorities[r.Priority]++ if r.Weight == 0 { g.zeroWeight[r.Priority]++ } } if len(groups) == 0 { return []sdk.CheckState{{Status: sdk.StatusInfo, Code: "srv_prio_weight_na", Message: "No active SRV records to analyse."}} } var noFailover []string var zeroWeightGroups []string for k, g := range groups { if len(g.priorities) < 2 { total := 0 for _, c := range g.priorities { total += c } if total > 1 { noFailover = append(noFailover, k.owner) } } for prio, zc := range g.zeroWeight { if zc > 1 && zc == g.priorities[prio] { zeroWeightGroups = append(zeroWeightGroups, fmt.Sprintf("%s@prio=%d", k.owner, prio)) } } } var out []sdk.CheckState if len(zeroWeightGroups) > 0 { out = append(out, sdk.CheckState{Status: sdk.StatusWarn, Code: "srv_weight_all_zero", Message: fmt.Sprintf("Priority group(s) with multiple targets all weighted 0: %s", strings.Join(zeroWeightGroups, ", "))}) } if len(noFailover) > 0 { out = append(out, sdk.CheckState{Status: sdk.StatusInfo, Code: "srv_single_priority", Message: fmt.Sprintf("Service(s) with multiple records but a single priority tier (no failover): %s", strings.Join(noFailover, ", "))}) } if len(out) == 0 { out = append(out, sdk.CheckState{Status: sdk.StatusOK, Code: "srv_prio_weight_ok", Message: "Priority and weight values are consistent with RFC 2782 conventions."}) } return out }