mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-11-04 08:40:20 +00:00
286 lines
6.9 KiB
Go
286 lines
6.9 KiB
Go
package ddns
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/libdns/libdns"
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/nezhahq/nezha/model"
|
|
)
|
|
|
|
type testSt struct {
|
|
domain string
|
|
zone string
|
|
prefix string
|
|
}
|
|
|
|
func TestSplitDomainSOA(t *testing.T) {
|
|
handler := dns.NewServeMux()
|
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Authoritative = true
|
|
if len(r.Question) > 0 {
|
|
qname := r.Question[0].Name
|
|
var zoneName string
|
|
if strings.HasSuffix(qname, "example.co.uk.") {
|
|
zoneName = "example.co.uk."
|
|
} else if strings.HasSuffix(qname, "example.com.") {
|
|
zoneName = "example.com."
|
|
}
|
|
|
|
if zoneName != "" {
|
|
soa := &dns.SOA{
|
|
Hdr: dns.RR_Header{Name: zoneName, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
|
|
Ns: "ns.example.com.",
|
|
Mbox: "admin.example.com.",
|
|
Serial: 1,
|
|
}
|
|
m.Answer = append(m.Answer, soa)
|
|
m.Ns = append(m.Ns, soa)
|
|
}
|
|
}
|
|
_ = w.WriteMsg(m)
|
|
})
|
|
|
|
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to listen: %v", err)
|
|
}
|
|
defer pc.Close()
|
|
|
|
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
|
|
go func() {
|
|
_ = server.ActivateAndServe()
|
|
}()
|
|
defer server.Shutdown()
|
|
|
|
addr := pc.LocalAddr().String()
|
|
|
|
cases := []testSt{
|
|
{
|
|
domain: "www.example.co.uk",
|
|
zone: "example.co.uk.",
|
|
prefix: "www",
|
|
},
|
|
{
|
|
domain: "abc.example.com",
|
|
zone: "example.com.",
|
|
prefix: "abc",
|
|
},
|
|
{
|
|
domain: "example.com",
|
|
zone: "example.com.",
|
|
prefix: "",
|
|
},
|
|
}
|
|
|
|
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{addr})
|
|
provider := &Provider{}
|
|
for _, c := range cases {
|
|
prefix, zone, err := provider.splitDomainSOA(ctx, c.domain)
|
|
if err != nil {
|
|
t.Fatalf("Error for domain %s: %s", c.domain, err)
|
|
}
|
|
if prefix != c.prefix {
|
|
t.Fatalf("Expected prefix %s, but got %s", c.prefix, prefix)
|
|
}
|
|
if zone != c.zone {
|
|
t.Fatalf("Expected zone %s, but got %s", c.zone, zone)
|
|
}
|
|
}
|
|
}
|
|
|
|
type MockSetter struct {
|
|
Records []libdns.Record
|
|
SetCalled bool
|
|
DelCalled bool
|
|
DeletedRecords []libdns.Record
|
|
}
|
|
|
|
func (m *MockSetter) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
m.SetCalled = true
|
|
m.Records = append(m.Records, records...)
|
|
return records, nil
|
|
}
|
|
|
|
func (m *MockSetter) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
m.DelCalled = true
|
|
m.DeletedRecords = append(m.DeletedRecords, records...)
|
|
return records, nil
|
|
}
|
|
|
|
type MockUnsupportedSetter struct{}
|
|
|
|
func (m *MockUnsupportedSetter) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
|
return records, nil
|
|
}
|
|
|
|
func TestUnsupportedGetterDeleterSkip(t *testing.T) {
|
|
provider := &Provider{
|
|
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
|
|
IPAddrs: &model.IP{IPv4Addr: ""},
|
|
Setter: &MockUnsupportedSetter{},
|
|
}
|
|
|
|
err := provider.deleteDomainRecord(context.Background(), "sub", "example.com", "A")
|
|
if err != nil {
|
|
t.Fatalf("expected no error for unsupported deleter skip, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIndependentDualStackProcessing(t *testing.T) {
|
|
enableIPv4 := true
|
|
enableIPv6 := true
|
|
mockSetter := &MockSetter{}
|
|
|
|
provider := &Provider{
|
|
DDNSProfile: &model.DDNSProfile{
|
|
MaxRetries: 1,
|
|
Domains: []string{"example.com"},
|
|
EnableIPv4: &enableIPv4,
|
|
EnableIPv6: &enableIPv6,
|
|
},
|
|
IPAddrs: &model.IP{
|
|
IPv4Addr: "1.1.1.1",
|
|
IPv6Addr: "2001:db8::1",
|
|
},
|
|
Setter: mockSetter,
|
|
}
|
|
|
|
handler := dns.NewServeMux()
|
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
m.Authoritative = true
|
|
if len(r.Question) > 0 {
|
|
soa := &dns.SOA{
|
|
Hdr: dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
|
|
Ns: "ns.example.com.",
|
|
Mbox: "admin.example.com.",
|
|
Serial: 1,
|
|
}
|
|
m.Answer = append(m.Answer, soa)
|
|
m.Ns = append(m.Ns, soa)
|
|
}
|
|
_ = w.WriteMsg(m)
|
|
})
|
|
|
|
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to listen: %v", err)
|
|
}
|
|
defer pc.Close()
|
|
|
|
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
|
|
go func() {
|
|
_ = server.ActivateAndServe()
|
|
}()
|
|
defer server.Shutdown()
|
|
|
|
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{pc.LocalAddr().String()})
|
|
provider.UpdateDomain(ctx)
|
|
|
|
if !mockSetter.SetCalled {
|
|
t.Fatalf("expected SetRecords to have been called for dual stack updates")
|
|
}
|
|
|
|
foundIPv4 := false
|
|
foundIPv6 := false
|
|
for _, rec := range mockSetter.Records {
|
|
if addrRec, ok := rec.(libdns.Address); ok {
|
|
if addrRec.IP.String() == "1.1.1.1" {
|
|
foundIPv4 = true
|
|
}
|
|
if addrRec.IP.String() == "2001:db8::1" {
|
|
foundIPv6 = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundIPv4 || !foundIPv6 {
|
|
t.Fatalf("expected both IPv4 and IPv6 records to be processed independently, got records: %v", mockSetter.Records)
|
|
}
|
|
}
|
|
|
|
func TestTransientSOAFailureAndRetry(t *testing.T) {
|
|
var attempts int64
|
|
handler := dns.NewServeMux()
|
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
|
atomic.AddInt64(&attempts, 1)
|
|
m := new(dns.Msg)
|
|
m.SetReply(r)
|
|
_ = w.WriteMsg(m)
|
|
})
|
|
|
|
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("failed to listen: %v", err)
|
|
}
|
|
defer pc.Close()
|
|
|
|
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
|
|
go func() {
|
|
_ = server.ActivateAndServe()
|
|
}()
|
|
defer server.Shutdown()
|
|
|
|
var maxRetries uint64 = 3
|
|
provider := &Provider{
|
|
DDNSProfile: &model.DDNSProfile{
|
|
MaxRetries: maxRetries,
|
|
Domains: []string{"example.com"},
|
|
EnableIPv4: &[]bool{true}[0],
|
|
EnableIPv6: &[]bool{false}[0],
|
|
},
|
|
IPAddrs: &model.IP{IPv4Addr: "1.1.1.1"},
|
|
Setter: &MockSetter{},
|
|
}
|
|
|
|
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{pc.LocalAddr().String()})
|
|
provider.UpdateDomain(ctx)
|
|
|
|
actualAttempts := atomic.LoadInt64(&attempts)
|
|
expectedAttempts := int64(maxRetries) * 2
|
|
if actualAttempts != expectedAttempts {
|
|
t.Fatalf("expected exact attempt count of %d, got %d", expectedAttempts, actualAttempts)
|
|
}
|
|
}
|
|
|
|
func TestGenericRecordDeleterContract(t *testing.T) {
|
|
mockSetter := &MockSetter{}
|
|
provider := &Provider{
|
|
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
|
|
IPAddrs: &model.IP{IPv4Addr: ""},
|
|
Setter: mockSetter,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
err := provider.deleteDomainRecord(ctx, "sub", "example.com", "A")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if !mockSetter.DelCalled {
|
|
t.Fatalf("expected DeleteRecords to be called")
|
|
}
|
|
|
|
if len(mockSetter.DeletedRecords) != 1 {
|
|
t.Fatalf("expected 1 record sent for deletion, got %d", len(mockSetter.DeletedRecords))
|
|
}
|
|
|
|
rr, ok := mockSetter.DeletedRecords[0].(libdns.RR)
|
|
if !ok {
|
|
t.Fatalf("expected deletion payload to be libdns.RR")
|
|
}
|
|
|
|
if rr.Name != "sub" || rr.Type != "A" {
|
|
t.Fatalf("unexpected RR fields: Name=%s, Type=%s", rr.Name, rr.Type)
|
|
}
|
|
}
|