Skip to content

Commit

Permalink
Add dns feature
Browse files Browse the repository at this point in the history
  • Loading branch information
mortymacs committed Feb 6, 2025
1 parent 7e0874c commit c84dafd
Show file tree
Hide file tree
Showing 27 changed files with 1,660 additions and 1 deletion.
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,64 @@ wait4x tcp 127.0.0.1:9090
```
This command waits until the TCP port `9090` on `127.0.0.1` is available.

### DNS

```shell
# Check A records existence
wait4x dns A wait4x.dev

# Check A records with expected ips
wait4x dns A wait4x.dev --expected-ip 172.67.154.180

# Check A records by defined nameserver
wait4x dns A wait4x.dev --expected-ip 172.67.154.180 -n gordon.ns.cloudflare.com

# Check AAAA records existence
wait4x dns AAAA wait4x.dev

# Check AAAA records with expected ips
wait4x dns AAAA wait4x.dev --expected-ip '2606:4700:3033::ac43:9ab4'

# Check AAAA records by defined nameserver
wait4x dns AAAA wait4x.dev --expected-ip '2606:4700:3033::ac43:9ab4' -n gordon.ns.cloudflare.com

# Check CNAME record existence
wait4x dns CNAME 172.67.154.180

# Check CNAME records with expected ips
wait4x dns CNAME 172.67.154.180 --expected-domain wait4x.dev

# Check CNAME record by defined nameserver
wait4x dns CNAME 172.67.154.180 --expected-domain wait4x.dev -n gordon.ns.cloudflare.com

# Check MX records existence
wait4x dns MX wait4x.dev

# Check MX records with expected domains
wait4x dns MX wait4x.dev --expected-domain 'route1.mx.cloudflare.net'

# Check MX records by defined nameserver
wait4x dns MX wait4x.dev --expected-domain 'route1.mx.cloudflare.net.' -n gordon.ns.cloudflare.com

# Check NS records existence
wait4x dns NS wait4x.dev

# Check NS records with expected nameservers
wait4x dns NS wait4x.dev --expected-nameserver 'emma.ns.cloudflare.com'

# Check NS records by defined nameserver
wait4x dns NS wait4x.dev --expected-nameserver 'emma.ns.cloudflare.com' -n gordon.ns.cloudflare.com

# Check TXT records existence
wait4x dns TXT wait4x.dev

# Check TXT records with expected values
wait4x dns TXT wait4x.dev --expected-value 'include:_spf.mx.cloudflare.net'

# Check TXT records by defined nameserver
wait4x dns TXT wait4x.dev --expected-value 'include:_spf.mx.cloudflare.net' -n gordon.ns.cloudflare.com
```

### HTTP

Check HTTP connection and expect a specific status code:
Expand Down Expand Up @@ -361,4 +419,4 @@ 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.
```
```
101 changes: 101 additions & 0 deletions checker/dns/a/a.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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"
"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,
resolver: net.DefaultResolver,
}

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

// Nameserver settings.
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 d.address, nil
}

// 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, WithNameServer("8.8.8.8:53"), WithExpectedIPV4s([]string{"172.67.154.180"}))
assert.Nil(t, d.Check(context.Background()))
}
101 changes: 101 additions & 0 deletions checker/dns/aaaa/aaaa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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"
"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,
resolver: net.DefaultResolver,
}

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

// Nameserver settings.
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 d.address, nil
}

// 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) {
d := New(server, WithNameServer("8.8.8.8:53"), WithExpectedIPV6s([]string{"2606:4700:3034::6815:591"}))
assert.Nil(t, d.Check(context.Background()))
}
Loading

0 comments on commit c84dafd

Please sign in to comment.