Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dns feature #181

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions checker/dns/a/a.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Wait4X Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package a

import (
"context"
"fmt"
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
"net"

"wait4x.dev/v2/checker"
)

// Option configures an DNS A records
type Option func(d *A)

// A represents DNS A data structure
type A struct {
nameserver string
address string
expectedIPs []string
resolver *net.Resolver
}

// New creates the DNS A checker
func New(address string, opts ...Option) checker.Checker {
d := &A{
address: address,
}

// apply the list of options to A
for _, opt := range opts {
opt(d)
}

// Nameserver settings.
d.resolver = net.DefaultResolver
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
if d.nameserver != "" {
d.resolver = &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, network, d.nameserver)
},
}
}

return d
}

// WithNameServer overrides the default nameserver
func WithNameServer(nameserver string) Option {
return func(d *A) {
d.nameserver = nameserver
}
}

// WithExpectedIPV4s sets expected IPv4s
func WithExpectedIPV4s(ips []string) Option {
return func(d *A) {
d.expectedIPs = ips
}
}

// Identity returns the identity of the checker
func (d *A) Identity() (string, error) {
return fmt.Sprintf("A %s %s", d.address, d.expectedIPs), nil
}
mortymacs marked this conversation as resolved.
Show resolved Hide resolved

// Check checks A DNS records
func (d *A) Check(ctx context.Context) (err error) {
ips, err := d.resolver.LookupIP(ctx, "ip4", d.address)
if err != nil {
return err
}

for _, ip := range ips {
if len(d.expectedIPs) == 0 {
return nil
}
for _, expectedIP := range d.expectedIPs {
if expectedIP == ip.String() {
return nil
}
}
}

return checker.NewExpectedError(
"the A record value doesn't expect", nil,
"actual", ips, "expect", d.expectedIPs,
)
}
32 changes: 32 additions & 0 deletions checker/dns/a/a_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package a

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"wait4x.dev/v2/checker"
)

const server = "wait4x.dev"

func TestCheckExistenceA(t *testing.T) {
d := New(server)
assert.Nil(t, d.Check(context.Background()))
}

func TestCorrectA(t *testing.T) {
d := New(server, WithExpectedIPV4s([]string{"172.67.154.180", "127.0.0.1"}))
assert.Nil(t, d.Check(context.Background()))
}

func TestIncorrectA(t *testing.T) {
var expectedError *checker.ExpectedError
d := New(server, WithExpectedIPV4s([]string{"127.0.0.1"}))
assert.ErrorAs(t, d.Check(context.Background()), &expectedError)
}

func TestCustomNSCorrectA(t *testing.T) {
d := New(server, WithExpectedIPV4s([]string{"172.67.154.180"}))
assert.Nil(t, d.Check(context.Background()))
}
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
102 changes: 102 additions & 0 deletions checker/dns/aaaa/aaaa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Wait4X Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aaaa

import (
"context"
"fmt"
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
"net"

"wait4x.dev/v2/checker"
)

// Option configures an DNS AAAA records
type Option func(d *AAAA)

// AAAA represents DNS AAAA data structure
type AAAA struct {
nameserver string
address string
expectedIPs []string
resolver *net.Resolver
}

// New creates the DNS AAAA checker
func New(address string, opts ...Option) checker.Checker {
d := &AAAA{
address: address,
}

// apply the list of options to AAAA
for _, opt := range opts {
opt(d)
}

// Nameserver settings.
d.resolver = net.DefaultResolver
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
if d.nameserver != "" {
d.resolver = &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, network, d.nameserver)
},
}
}

return d
}

// WithNameServer overrides the default nameserver
func WithNameServer(nameserver string) Option {
return func(d *AAAA) {
d.nameserver = nameserver
}
}

// WithExpectedIPV6s sets expected IPv6s
func WithExpectedIPV6s(ips []string) Option {
return func(d *AAAA) {
d.expectedIPs = ips
}
}

// Identity returns the identity of the checker
func (d *AAAA) Identity() (string, error) {
return fmt.Sprintf("AAAA %s %s", d.address, d.expectedIPs), nil
}
mortymacs marked this conversation as resolved.
Show resolved Hide resolved

// Check checks DNS records
func (d *AAAA) Check(ctx context.Context) (err error) {
values, err := d.resolver.LookupIP(ctx, "ip6", d.address)
if err != nil {
return err
}

for _, ip := range values {
if len(d.expectedIPs) == 0 {
return nil
}
for _, expectedIP := range d.expectedIPs {
if expectedIP == ip.String() {
return nil
}
}
}

return checker.NewExpectedError(
"the AAAA record value doesn't expect", nil,
"actual", values, "expect", d.expectedIPs,
)
}
32 changes: 32 additions & 0 deletions checker/dns/aaaa/aaaa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package aaaa

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"wait4x.dev/v2/checker"
)

const server = "wait4x.dev"

func TestCheckExistenceAAAA(t *testing.T) {
d := New(server)
assert.Nil(t, d.Check(context.Background()))
}

func TestCorrectAAAA(t *testing.T) {
d := New(server, WithExpectedIPV6s([]string{"2606:4700:3034::6815:591"}))
assert.Nil(t, d.Check(context.Background()))
}

func TestIncorrectAAAA(t *testing.T) {
var expectedError *checker.ExpectedError
d := New(server, WithExpectedIPV6s([]string{"127.0.0.1"}))
assert.ErrorAs(t, d.Check(context.Background()), &expectedError)
}

func TestCustomNSCorrectAAAA(t *testing.T) {
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
d := New(server, WithExpectedIPV6s([]string{"2606:4700:3034::6815:591"}))
assert.Nil(t, d.Check(context.Background()))
}
102 changes: 102 additions & 0 deletions checker/dns/cname/cname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2023 The Wait4X Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cname

import (
"context"
"fmt"
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
"net"
"regexp"

"wait4x.dev/v2/checker"
)

// Option configures an DNS CNAME record
type Option func(d *CNAME)

// CNAME represents DNS CNAME data structure
type CNAME struct {
nameserver string
address string
expectedDomains []string
resolver *net.Resolver
}

// New creates the DNS CNAME checker
func New(address string, opts ...Option) checker.Checker {
d := &CNAME{
address: address,
}

// apply the list of options to CNAME
for _, opt := range opts {
opt(d)
}

// Nameserver settings.
d.resolver = net.DefaultResolver
mortymacs marked this conversation as resolved.
Show resolved Hide resolved
if d.nameserver != "" {
d.resolver = &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, network, d.nameserver)
},
}
}

return d
}

// WithNameServer overrides the default nameserver
func WithNameServer(nameserver string) Option {
return func(d *CNAME) {
d.nameserver = nameserver
}
}

// WithExpectedDomains sets expected domains
func WithExpectedDomains(doamins []string) Option {
return func(d *CNAME) {
d.expectedDomains = doamins
}
}

// Identity returns the identity of the checker
func (d *CNAME) Identity() (string, error) {
return fmt.Sprintf("CNAME %s %s", d.address, d.expectedDomains), nil
}
mortymacs marked this conversation as resolved.
Show resolved Hide resolved

// Check checks DNS TXT records
func (d *CNAME) Check(ctx context.Context) (err error) {
value, err := d.resolver.LookupCNAME(ctx, d.address)
if err != nil {
return err
}

if len(value) != 0 && len(d.expectedDomains) == 0 {
return nil
}
for _, expectedDomain := range d.expectedDomains {
matched, _ := regexp.MatchString(expectedDomain, value)
if matched {
return nil
}
}

return checker.NewExpectedError(
"the CNAME record value doesn't expect", nil,
"actual", value, "expect", d.expectedDomains,
)
}
Loading