Skip to content

Commit

Permalink
Merge pull request #7 from headincloud/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jeroenjacobs79 authored Jun 26, 2021
2 parents fdbd200 + 38b43a7 commit 18d05ed
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ LD_FLAGS="-s -w -X main.Version=$(VERSION)"
# Targets we want to build
PLATFORMS=linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/ppc64 linux/riscv64 \
freebsd/386 freebsd/amd64 freebsd/arm freebsd/arm64 \
openbsd/386 openbsd/amd64 openbsd/arm openbsd/arm64 \
openbsd/386 openbsd/amd64 openbsd/arm openbsd/arm64 openbsd/mips64 \
netbsd/386 netbsd/amd64 netbsd/arm netbsd/arm64 \
darwin/amd64 windows/amd64 \
darwin/amd64 windows/amd64 darwin/arm64 \
solaris/amd64

# Used for help output
Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,22 @@ To be clear: I do NOT include malware in the executable!**
./cf-ddns-agent
-cf-api-token string
Specify the CloudFlare API token. Using this parameter is discouraged, and the token should be specified in CF_API_TOKEN environment variable.
-create
Create record with 'auto' TTL if it doesn't exist yet, or generate error otherwise. (default "true") (default true)
-daemon
Run continuously in background and perform update every <x> minutes. (see 'update-interval')
-discovery-url string
Specify an alternative IPv4 discovery service. (default "https://api.ipify.org")
-discovery-url-v6 string
Specify an alternative IPv6 discovery service. (default "https://api64.ipify.org")
-domain string
Specify the CloudFlare domain. (example: 'mydomain.org') - REQUIRED
Specify the CloudFlare domain. (example: 'mydomain.org') - REQUIRED.
-dry-run
Run in "dry-run" mode, don't actually update the record. (default "false")
-host string
Specify the full A record name that needs to be updated. (example: 'myhost.mydomain.org') - REQUIRED
Specify the full A record name that needs to be updated. (example: 'myhost.mydomain.org') - REQUIRED.
-ipv6
Enable ipv6 support and CAA record updates, check README. (default "false")
-update-interval int
Specify interval (in minutes) for updating the DNS record when running as a daemon. (see 'daemon'). A minimum of 5 is enforced. (default 15)
````
Expand All @@ -50,10 +58,16 @@ If setting the `CF_API_TOKEN` is not possible for some reason, it is possible to
- In case of a 4xx http error, no retry occurs as this probably means a configuration issue (invalid discovery url supplied).


## Ipv6 support

This software now includes experimental ipv6 support. you can enable it with the `-ipv6=true` parameter. This will update the AAAA-record for the specified host.

**Attention: Most operating systems use a temporary ipv6 address for outbound connections. This feature needs to be disabled in order for `cf-ddns-agent` to work correctly! Check your operating system's manual.**

## Roadmap

- IPv6 and AAAA record support
- Multiple IPv4 discovery providers
- Improve IPv6 and AAAA record support
- Multiple IP discovery providers


## License and copyright
Expand Down Expand Up @@ -81,3 +95,4 @@ The following third-party software is also directly included:

- sirupsen/logrus (c) Simon Eskildsen, MIT license. See: https://github.com/sirupsen/logrus
- cloudflare/cloudflare-go (c) CloudFlare, BSD license. See: https://github.com/cloudflare/cloudflare-go
- asaskevich/govalidator (c) Alex Saskevich, MIT license. See: https://github.com/asaskevich/govalidator
2 changes: 1 addition & 1 deletion build/ci/travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dist: bionic
language: go
go:
- 1.15.x
- 1.16.x
sudo: false
if: branch != main
jobs:
Expand Down
24 changes: 19 additions & 5 deletions cmd/cf-ddns-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ func main() {
}

log.Infof("%s (version %s) is starting...\n", AppName, Version)
log.Infof("IP Discovery service url is set to: %s", Options.DiscoveryURL)
log.Infof("IPv4 Discovery service url is set to: %s", Options.DiscoveryURL)
if Options.Ipv6Enabled {
log.Infof("IPv6 Discovery service url is set to: %s", Options.DiscoveryURLv6)
}

if Options.Domain == "" || Options.Host == "" {
log.Fatal("Both --domain and --host must be set!")
Expand Down Expand Up @@ -107,13 +110,24 @@ func Exit(err error) {

func PerformUpdate() (err error) {
// get ip and update
MyIP, err := discovery.DiscoverIPv4(Options.DiscoveryURL)
MyIPv4, err := discovery.DiscoverIPv4(Options.DiscoveryURL)
if err != nil {
log.Errorf("An error was encountered during IP discovery. Check previous log entries for more details.")
log.Errorf("An error was encountered during IPv4 discovery. Check previous log entries for more details.")
} else {
err = util.PerformRecordUpdate(Options.CfAPIToken, Options.Domain, Options.Host, MyIP)
err = util.UpdateCFRecord(Options.CfAPIToken, Options.Domain, Options.Host, "A", MyIPv4, Options.DryRun, Options.CreateMode)
if err != nil {
log.Error("An error was encountered during updating of the DNS record. Check previous log entries for more details.")
log.Error("An error was encountered during updating of the DNS A-record. Check previous log entries for more details.")
}
}
if Options.Ipv6Enabled {
MyIPv6, err := discovery.DiscoverIPv6(Options.DiscoveryURLv6)
if err != nil {
log.Errorf("An error was encountered during IPv4 discovery. Check previous log entries for more details.")
} else {
err = util.UpdateCFRecord(Options.CfAPIToken, Options.Domain, Options.Host, "AAAA", MyIPv6, Options.DryRun, Options.CreateMode)
if err != nil {
log.Error("An error was encountered during updating of the DNS AAAA-record. Check previous log entries for more details.")
}
}
}
return
Expand Down
11 changes: 8 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
module github.com/headincloud/cf-ddns-agent

go 1.13
go 1.16

require (
github.com/cloudflare/cloudflare-go v0.13.4
github.com/sirupsen/logrus v1.7.0
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/cloudflare/cloudflare-go v0.18.0
github.com/sirupsen/logrus v1.8.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
)
28 changes: 28 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cloudflare/cloudflare-go v0.13.4 h1:3Dm3p31K/BxKZhy+Ll2Pf7yStprJtgBKi52ee0NPcAU=
github.com/cloudflare/cloudflare-go v0.13.4/go.mod h1:jGTn0jEGfm8MVoTjBdbVDPHDkLmHdvcVIbYWTklYTvs=
github.com/cloudflare/cloudflare-go v0.18.0 h1:9q1yV4XuYqAZKsMUygRFH1rmmDq5rpaVXL+WWfeliao=
github.com/cloudflare/cloudflare-go v0.18.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -15,29 +24,48 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 changes: 11 additions & 2 deletions pkg/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import "flag"

type ProgramOptions struct {
DiscoveryURL string
DiscoveryURLv6 string
Ipv6Enabled bool
DryRun bool
CreateMode bool
Domain string
Host string
CfAPIToken string
Expand All @@ -28,13 +32,18 @@ type ProgramOptions struct {
}

var DefaultDiscoveryURL = "https://api.ipify.org"
var DefaultDiscoveryURLv6 = "https://api64.ipify.org"

func InitConfig(Options *ProgramOptions) {
flag.StringVar(&Options.DiscoveryURL, "discovery-url", DefaultDiscoveryURL, "Specify an alternative IPv4 discovery service.")
flag.StringVar(&Options.Domain, "domain", "", "Specify the CloudFlare domain. (example: 'mydomain.org') - REQUIRED")
flag.StringVar(&Options.Host, "host", "", "Specify the full A record name that needs to be updated. (example: 'myhost.mydomain.org') - REQUIRED")
flag.StringVar(&Options.DiscoveryURLv6, "discovery-url-v6", DefaultDiscoveryURLv6, "Specify an alternative IPv6 discovery service.")
flag.BoolVar(&Options.Ipv6Enabled, "ipv6", false, "Enable ipv6 support and CAA record updates, check README. (default \"false\")")
flag.StringVar(&Options.Domain, "domain", "", "Specify the CloudFlare domain. (example: 'mydomain.org') - REQUIRED.")
flag.StringVar(&Options.Host, "host", "", "Specify the full A record name that needs to be updated. (example: 'myhost.mydomain.org') - REQUIRED.")
flag.StringVar(&Options.CfAPIToken, "cf-api-token", "", "Specify the CloudFlare API token. Using this parameter is discouraged, and the token should be specified in CF_API_TOKEN environment variable.")
flag.BoolVar(&Options.Daemon, "daemon", false, "Run continuously in background and perform update every <x> minutes. (see 'update-interval')")
flag.IntVar(&Options.UpdateInterval, "update-interval", 15, "Specify interval (in minutes) for updating the DNS record when running as a daemon. (see 'daemon'). A minimum of 5 is enforced.")
flag.BoolVar(&Options.DryRun, "dry-run", false, "Run in \"dry-run\" mode, don't actually update the record. (default \"false\")")
flag.BoolVar(&Options.CreateMode, "create", true, "Create record with 'auto' TTL if it doesn't exist yet, or generate error otherwise. (default \"true\")")
flag.Parse()
}
64 changes: 60 additions & 4 deletions pkg/discovery/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/http"
"time"

valid "github.com/asaskevich/govalidator"
log "github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -67,13 +68,68 @@ func DiscoverIPv4(DiscoveryURL string) (ip net.IP, err error) {
log.Errorf("could not read response from IP discovery service: %s", err.Error())
return
}
ip = net.ParseIP(string(body))
if ip == nil {
err = fmt.Errorf("could not parse received value as an IP address")
if valid.IsIPv4(string(body)) {
ip = net.ParseIP(string(body))
if ip == nil {
err = fmt.Errorf("could not parse received value as an IPv4 address")
log.Error(err.Error())
return
}
log.Infof("IP address received: %s", ip)
}
return
}

func DiscoverIPv6(DiscoveryURL string) (ip net.IP, err error) {
currentDelay := 10 * time.Second
incrementDelay := 10 * time.Second
retries := 3
// get ip
log.Infof("Contacting the IPv6 discovery service (%s)...", DiscoveryURL)
resp, retryable, err := RetryableGet(DiscoveryURL)
if err != nil {
log.Error(err.Error())
if retryable {
for count := 0; count < retries; count++ {
log.Infof("will retry in %s", currentDelay.String())
time.Sleep(currentDelay)
// action
resp, retryable, err = RetryableGet(DiscoveryURL)
if err != nil {
log.Error(err.Error())
if retryable {
currentDelay += incrementDelay
continue
} else {
// if not retryable, break loop
break
}
} else {
// if no error, we can break loop as well
break
}
}
}
}
// if still error, return
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Errorf("could not read response from IP discovery service: %s", err.Error())
return
}
log.Infof("IP address received: %s", ip)
if valid.IsIPv6(string(body)) {
ip = net.ParseIP(string(body))
if ip == nil {
err = fmt.Errorf("could not parse received value as an IPv6 address")
log.Error(err.Error())
return
}
log.Infof("IP address received: %s", ip)
}
return
}

Expand Down
Loading

0 comments on commit 18d05ed

Please sign in to comment.