checker: implement ShareKey to mutualise reverse-DNS lookups across targets
The reverse-DNS observation (zone location, authoritative NS, the PTR RRset, and the forward-confirm of the effective target) is determined entirely by the reverse-arpa owner name — i.e. the IP being asked about — never by which forward domain triggered the check. Implement sdk.ObservationSharer so the host runs the PTR + FCrDNS lookups once and serves every target that interrogates the same reverse name, instead of re-querying per record. The share key derives from the owner name and folds in the declared target and TTL: these are part of the observation and can change it — when no PTR is published the effective target (and therefore its forward-confirm) falls back to the declared value. ShareKey stays a pure function of opts (no network) per the contract. An empty owner yields "" so the host falls back to per-target caching.
This commit is contained in:
parent
5f454fa062
commit
ab1595d85f
2 changed files with 71 additions and 0 deletions
|
|
@ -2,6 +2,8 @@ package checker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -95,6 +97,28 @@ func (p *ptrProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShareKey implements sdk.ObservationSharer. The reverse-DNS observation (zone
|
||||||
|
// location, authoritative NS, the PTR RRset, and the forward-confirm of the
|
||||||
|
// effective target) is determined entirely by the reverse-arpa owner name — i.e.
|
||||||
|
// the IP being asked about — never by which forward domain triggered the check.
|
||||||
|
// Two targets that interrogate the same reverse name produce identical data, so
|
||||||
|
// the host can run the PTR + FCrDNS lookups once and serve the rest.
|
||||||
|
//
|
||||||
|
// The declared target and TTL are folded in because they are part of the
|
||||||
|
// observation and can change it: when no PTR is published the effective target
|
||||||
|
// (and therefore its forward-confirm) falls back to the declared value. This
|
||||||
|
// stays a pure function of opts (no network) per the contract. An empty owner
|
||||||
|
// returns "" so the host falls back to the default per-target caching.
|
||||||
|
func (p *ptrProvider) ShareKey(opts sdk.CheckerOptions) (string, error) {
|
||||||
|
owner, declaredTarget, declaredTTL, err := resolvePTRInputs(opts)
|
||||||
|
if err != nil || owner == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.Sum256(fmt.Appendf(nil, "%s|%s|%d", lowerFQDN(owner), declaredTarget, declaredTTL))
|
||||||
|
return "ptr:" + hex.EncodeToString(h[:8]), nil
|
||||||
|
}
|
||||||
|
|
||||||
// resolvePTRInputs extracts the PTR owner, declared target and TTL from the
|
// resolvePTRInputs extracts the PTR owner, declared target and TTL from the
|
||||||
// auto-filled options.
|
// auto-filled options.
|
||||||
func resolvePTRInputs(opts sdk.CheckerOptions) (owner, target string, ttl uint32, err error) {
|
func resolvePTRInputs(opts sdk.CheckerOptions) (owner, target string, ttl uint32, err error) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package checker
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||||
|
|
@ -158,3 +159,49 @@ func TestResolvePTRInputs_WrongServiceType(t *testing.T) {
|
||||||
t.Fatal("expected error for non-PTR service type")
|
t.Fatal("expected error for non-PTR service type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShareKey_StableAndPrefixed(t *testing.T) {
|
||||||
|
p := &ptrProvider{}
|
||||||
|
opts := sdk.CheckerOptions{"domain_name": "4.3.2.1.in-addr.arpa"}
|
||||||
|
a, err := p.ShareKey(opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b, _ := p.ShareKey(opts)
|
||||||
|
if a != b {
|
||||||
|
t.Fatalf("non-deterministic key: %q vs %q", a, b)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(a, "ptr:") {
|
||||||
|
t.Fatalf("missing prefix: %q", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShareKey_DiffersByOwner(t *testing.T) {
|
||||||
|
p := &ptrProvider{}
|
||||||
|
a, _ := p.ShareKey(sdk.CheckerOptions{"domain_name": "4.3.2.1.in-addr.arpa"})
|
||||||
|
b, _ := p.ShareKey(sdk.CheckerOptions{"domain_name": "5.3.2.1.in-addr.arpa"})
|
||||||
|
if a == b {
|
||||||
|
t.Fatalf("expected different keys for different owners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShareKey_DiffersByDeclaredTarget(t *testing.T) {
|
||||||
|
p := &ptrProvider{}
|
||||||
|
base := sdk.CheckerOptions{"domain_name": "4.3.2.1.in-addr.arpa"}
|
||||||
|
a, _ := p.ShareKey(base)
|
||||||
|
b, _ := p.ShareKey(sdk.CheckerOptions{"domain_name": "4.3.2.1.in-addr.arpa", "expected_target": "host.example.org"})
|
||||||
|
if a == b {
|
||||||
|
t.Fatalf("declared target must affect the key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShareKey_EmptyWhenUnresolvable(t *testing.T) {
|
||||||
|
p := &ptrProvider{}
|
||||||
|
sk, err := p.ShareKey(sdk.CheckerOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if sk != "" {
|
||||||
|
t.Fatalf("expected empty key, got %q", sk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue