Skip to content

Commit

Permalink
change(publicip/dns): use DNS over TLS only
Browse files Browse the repository at this point in the history
- Fix critical issue #492
- Remove `google` dns provider since it does not support DNS over TLS
  • Loading branch information
qdm12 committed Jun 17, 2023
1 parent 828373d commit 320d91d
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 107 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ You can otherwise customize it with the following:
- `noip` using [http://ip1.dynupdate6.no-ip.com](http://ip1.dynupdate6.no-ip.com)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `PUBLICIP_DNS_PROVIDERS` gets your public IPv4 address only or IPv6 address only or one of them (see #136). It can be one or more of the following:
- `google`
- `cloudflare`
- `opendns`

Expand Down
4 changes: 2 additions & 2 deletions cmd/updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func _main(ctx context.Context, settingsSource SettingsSource, args []string, lo
return client.Query(ctx, *healthSettings.ServerAddress)
}

announcementExp, err := time.Parse(time.RFC3339, "2023-06-30T00:00:00Z")
announcementExp, err := time.Parse(time.RFC3339, "2023-07-15T00:00:00Z")
if err != nil {
return err
}
Expand All @@ -118,7 +118,7 @@ func _main(ctx context.Context, settingsSource SettingsSource, args []string, lo
Version: buildInfo.Version,
Commit: buildInfo.Commit,
BuildDate: buildInfo.Created,
Announcement: "Environment variables parsing was changed on 12 June, please report any issue you might have",
Announcement: "Public IP dns provider GOOGLE, see https://github.com/qdm12/ddns-updater/issues/492",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/go-chi/chi v4.1.2+incompatible
github.com/golang/mock v1.6.0
github.com/miekg/dns v1.1.54
github.com/qdm12/gosettings v0.3.0
github.com/qdm12/gosettings v0.4.0-rc1
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/qdm12/gosettings v0.3.0 h1:YutcgQzVaOB3LuLj+Smtoy90JOH/5B5p2IH3BvV3ra4=
github.com/qdm12/gosettings v0.3.0/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
Expand Down
2 changes: 1 addition & 1 deletion internal/config/settings/pubip.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (p *PubIP) toLinesNode() (node *gotree.Node) {
node.Appendf("DNS enabled: %s", gosettings.BoolToYesNo(p.DNSEnabled))
if *p.DNSEnabled {
node.Appendf("DNS timeout: %s", p.DNSTimeout)
childNode := node.Appendf("DNS providers")
childNode := node.Appendf("DNS over TLS providers")
for _, provider := range p.DNSProviders {
childNode.Appendf(provider)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/config/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Test_Settings_String(t *testing.T) {
| | └── all
| ├── DNS enabled: yes
| ├── DNS timeout: 3s
| └── DNS providers
| └── DNS over TLS providers
| └── all
├── Resolver: use Go default resolver
├── IPv6
Expand Down
12 changes: 12 additions & 0 deletions internal/config/sources/env/pubip.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ func (s *Source) readPubIP() (settings settings.PubIP, err error) {
}

settings.DNSProviders = s.env.CSV("PUBLICIP_DNS_PROVIDERS")

// Retro-compatibility
for i, provider := range settings.DNSProviders {
if provider == "google" {
s.warner.Warnf("dns provider google will be ignored " +
"since it is no longer supported, " +
"see https://github.com/qdm12/ddns-updater/issues/492")
settings.DNSProviders[i] = settings.DNSProviders[len(settings.DNSProviders)-1]
settings.DNSProviders = settings.DNSProviders[:len(settings.DNSProviders)-1]
}
}

settings.DNSTimeout, err = s.env.Duration("PUBLICIP_DNS_TIMEOUT")
if err != nil {
return settings, err
Expand Down
30 changes: 3 additions & 27 deletions pkg/publicip/dns/dns.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package dns

import (
"net"

"github.com/miekg/dns"
)
import "time"

type Fetcher struct {
ring ring
client Client
client4 Client
client6 Client
timeout time.Duration
}

type ring struct {
Expand All @@ -28,29 +22,11 @@ func New(options ...Option) (f *Fetcher, err error) {
}
}

dialer := &net.Dialer{
Timeout: settings.timeout,
}

return &Fetcher{
ring: ring{
counter: new(uint32),
providers: settings.providers,
},
client: &dns.Client{
Net: "udp",
Dialer: dialer,
Timeout: settings.timeout,
},
client4: &dns.Client{
Net: "udp4",
Dialer: dialer,
Timeout: settings.timeout,
},
client6: &dns.Client{
Net: "udp6",
Dialer: dialer,
Timeout: settings.timeout,
},
timeout: settings.timeout,
}, nil
}
3 changes: 0 additions & 3 deletions pkg/publicip/dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,4 @@ func Test_New(t *testing.T) {

assert.NotNil(t, impl.ring.counter)
assert.NotEmpty(t, impl.ring.providers)
assert.NotNil(t, impl.client)
assert.NotNil(t, impl.client4)
assert.NotNil(t, impl.client6)
}
21 changes: 18 additions & 3 deletions pkg/publicip/dns/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,36 @@ import (
"context"
"errors"
"fmt"
"net"
"net/netip"

"github.com/miekg/dns"
)

var (
ErrNetworkNotSupported = errors.New("network not supported")
ErrAnswerNotReceived = errors.New("response answer not received")
ErrAnswerTypeMismatch = errors.New("answer type is not expected")
ErrAnswerTypeNotSupported = errors.New("answer type not supported")
ErrRecordEmpty = errors.New("record is empty")
ErrIPMalformed = errors.New("IP address malformed")
)

func fetch(ctx context.Context, client Client, providerData providerData) (
publicIPs []netip.Addr, err error) {
func fetch(ctx context.Context, client Client, network string,
providerData providerData) (publicIPs []netip.Addr, err error) {
var serverHost string
switch network {
case "tcp":
serverHost = providerData.TLSName
case "tcp4":
serverHost = providerData.IPv4.String()
case "tcp6":
serverHost = providerData.IPv6.String()
default:
return nil, fmt.Errorf("%w: %s", ErrNetworkNotSupported, network)
}
serverAddress := net.JoinHostPort(serverHost, "853")

message := &dns.Msg{
MsgHdr: dns.MsgHdr{
Opcode: dns.OpcodeQuery,
Expand All @@ -32,7 +47,7 @@ func fetch(ctx context.Context, client Client, providerData providerData) (
},
}

r, _, err := client.ExchangeContext(ctx, message, providerData.nameserver)
r, _, err := client.ExchangeContext(ctx, message, serverAddress)
if err != nil {
return nil, err
}
Expand Down
15 changes: 9 additions & 6 deletions pkg/publicip/dns/fetch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dns
import (
"context"
"errors"
"net"
"net/netip"
"testing"
"time"
Expand All @@ -18,10 +19,10 @@ func Test_fetch(t *testing.T) {
t.Parallel()

providerData := providerData{
nameserver: "nameserver",
fqdn: "record",
class: dns.ClassNONE,
qType: dns.Type(dns.TypeTXT),
TLSName: "nameserver",
fqdn: "record",
class: dns.ClassNONE,
qType: dns.Type(dns.TypeTXT),
}

expectedMessage := &dns.Msg{
Expand Down Expand Up @@ -101,11 +102,13 @@ func Test_fetch(t *testing.T) {
ctx := context.Background()

client := mock_dns.NewMockClient(ctrl)
expectedAddress := net.JoinHostPort(providerData.TLSName, "853")
client.EXPECT().
ExchangeContext(ctx, expectedMessage, providerData.nameserver).
ExchangeContext(ctx, expectedMessage, expectedAddress).
Return(testCase.response, time.Millisecond, testCase.exchangeErr)

publicIPs, err := fetch(ctx, client, providerData)
const network = "tcp" // so it picks the TLSName field as the address
publicIPs, err := fetch(ctx, client, network, providerData)

if testCase.err != nil {
require.Error(t, err)
Expand Down
9 changes: 2 additions & 7 deletions pkg/publicip/dns/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func Test_integration(t *testing.T) {
t.Parallel()

fetcher, err := New(SetProviders(Google, Cloudflare, OpenDNS))
fetcher, err := New(SetProviders(Cloudflare, OpenDNS))
require.NoError(t, err)

ctx := context.Background()
Expand All @@ -27,12 +27,7 @@ func Test_integration(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, publicIP2)

publicIP3, err := fetcher.IP4(ctx)
require.NoError(t, err)
assert.NotNil(t, publicIP2)

assert.Equal(t, publicIP1, publicIP2)
assert.Equal(t, publicIP1, publicIP3)
assert.Equal(t, publicIP1.String(), publicIP2.String())

t.Logf("Public IP is %s", publicIP1)
}
25 changes: 19 additions & 6 deletions pkg/publicip/dns/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ package dns

import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/netip"
"sync/atomic"

"github.com/miekg/dns"
)

var (
ErrIPNotFoundForVersion = errors.New("IP addresses found but not for IP version")
)

func (f *Fetcher) IP(ctx context.Context) (publicIP netip.Addr, err error) {
publicIPs, err := f.ip(ctx, f.client)
publicIPs, err := f.ip(ctx, "tcp")
if err != nil {
return netip.Addr{}, err
}
return publicIPs[0], nil
}

func (f *Fetcher) IP4(ctx context.Context) (publicIP netip.Addr, err error) {
publicIPs, err := f.ip(ctx, f.client4)
publicIPs, err := f.ip(ctx, "tcp4")
if err != nil {
return netip.Addr{}, err
}
Expand All @@ -35,7 +38,7 @@ func (f *Fetcher) IP4(ctx context.Context) (publicIP netip.Addr, err error) {
}

func (f *Fetcher) IP6(ctx context.Context) (publicIP netip.Addr, err error) {
publicIPs, err := f.ip(ctx, f.client6)
publicIPs, err := f.ip(ctx, "tcp6")
if err != nil {
return netip.Addr{}, err
}
Expand All @@ -48,9 +51,19 @@ func (f *Fetcher) IP6(ctx context.Context) (publicIP netip.Addr, err error) {
return netip.Addr{}, fmt.Errorf("%w: ipv6", ErrIPNotFoundForVersion)
}

func (f *Fetcher) ip(ctx context.Context, client Client) (
func (f *Fetcher) ip(ctx context.Context, network string) (
publicIPs []netip.Addr, err error) {
index := int(atomic.AddUint32(f.ring.counter, 1)) % len(f.ring.providers)
provider := f.ring.providers[index]
return fetch(ctx, client, provider.data())
providerData := f.ring.providers[index].data()

client := &dns.Client{
Net: network + "-tls",
Timeout: f.timeout,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: providerData.TLSName,
},
}

return fetch(ctx, client, network, providerData)
}
10 changes: 5 additions & 5 deletions pkg/publicip/dns/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ func Test_SetProviders(t *testing.T) {
initialSettings: settings{
providers: []Provider{Cloudflare},
},
providers: []Provider{Google},
providers: []Provider{OpenDNS},
expectedSettings: settings{
providers: []Provider{Google},
providers: []Provider{OpenDNS},
},
},
"Google and Cloudflare": {
"OpenDNS and Cloudflare": {
initialSettings: settings{
providers: []Provider{Cloudflare},
},
providers: []Provider{Google, Cloudflare},
providers: []Provider{OpenDNS, Cloudflare},
expectedSettings: settings{
providers: []Provider{Cloudflare, Google},
providers: []Provider{Cloudflare, OpenDNS},
},
},
"invalid provider": {
Expand Down
Loading

0 comments on commit 320d91d

Please sign in to comment.