Giter Site home page Giter Site logo

zebradil / cloudflare-dynamic-dns Goto Github PK

View Code? Open in Web Editor NEW
55.0 4.0 5.0 363 KB

Dynamic DNS client for Cloudflare with IPv6/IPv4 support

License: MIT License

Go 93.76% Dockerfile 1.23% Shell 5.01%
cloudflare dns dynamic-dns dynamic-dns-client systemd-timer

cloudflare-dynamic-dns's Introduction

Dynamic DNS client for Cloudflare

A CLI tool for updating A/AAAA record at Cloudflare DNS with the currently detected address of the specified network interface.

Features

  • Supports:
    • IPv4 and IPv6
    • Multiple domains with the same address
    • Multiple hosts in the same domain
  • Tries to be smart about selecting the address to use
  • Includes systemd service and timer files for automation
  • Can be run in a Docker container
  • Configuration via command line arguments, config file or environment variables

Usage

The rest of this section is the output of cloudflare-dynamic-dns --help.

Selects an address from the specified network interface and updates A or AAAA
records at Cloudflare for the configured domains. Supports both IPv4 and IPv6.

Required configuration options
--------------------------------------------------------------------------------

--iface:   network interface name to look up for an address
--domains: one or more domain names to assign the address to
--token:   Cloudflare API token with edit access rights to the DNS zone

IPv6 address selection
--------------------------------------------------------------------------------

When multiple IPv6 addresses are found on the interface, the following rules are
used to select the one to use:
    1. Only global unicast addresses (GUA) and unique local addresses (ULA) are
       considered.
    2. GUA addresses are preferred over ULA addresses.
    3. Unique EUI-64 addresses are preferred over randomly generated addresses.
    4. If priority subnets are specified, addresses from the subnet with the
       highest priority are selected. The priority is determined by the order of
       subnets specified on the command line or in the config file.

IPv4 address selection
--------------------------------------------------------------------------------

When multiple IPv4 addresses are found on the interface, the following rules are
used to select the one to use:
    1. All IPv4 addresses are considered.
    2. Public addresses are preferred over Shared Address Space (RFC 6598)
       addresses.
    3. Shared Address Space addresses are preferred over private addresses.
    4. Private addresses are preferred over loopback addresses.
    5. If priority subnets are specified, addresses from the subnet with the
       highest priority are selected. The priority is determined by the order of
       subnets specified on the command line or in the config file.

Non-public addresses are logged as warnings but are still used. They can be
useful in private networks or when using a VPN.

NOTE: Cloudflare doesn't allow proxying of records with non-public addresses.

Daemon mode
--------------------------------------------------------------------------------

By default, the program runs once and exits. This mode of operation can be
changed by setting the --run-every flag to a duration greater than 1m. In this
case, the program will run repeatedly, waiting the duration between runs. It
will stop if killed or if failed.

State file
--------------------------------------------------------------------------------

Setting --state-file makes the program to retain the previously used address
between runs to avoid unnecessary calls to the Cloudflare API.

The value is used as the state file path. When used with an empty value, the
state file is named after the interface name and the domains, and is stored
either in the current directory or in the directory specified by the
STATE_DIRECTORY environment variable.

The STATE_DIRECTORY environment variable is automatically set by systemd. It
can be set manually when running the program outside of systemd.

Multihost mode (EXPERIMENTAL)
--------------------------------------------------------------------------------

In this mode, it is possible to assign multiple addresses to a single or
multiple domains. For correct operation, this mode must be enabled on all hosts
participating in the same domain and different host-ids must be specified for
each host (see --host-id option). This mode is enabled by passing --multihost
flag.

In the multihost mode, the program will manage only the DNS records that have
the same host-id as the one specified on the command line or in the config file.
If an existing record has no host-id but has the same address as the target one,
it will be claimed by this host via setting the corresponding host-id. Any other
records will be ignored. This allows multiple hosts to share the same domain
without interfering with each other. The host-id is stored in the Cloudflare DNS
comments field (see https://developers.cloudflare.com/dns/manage-dns-records/reference/record-attributes/).

Persistent configuration
--------------------------------------------------------------------------------

The program can be configured using a config file. The default location is
$HOME/.cloudflare-dynamic-dns.yaml. The config file location can be overridden
using the --config flag. The config file format is YAML. The following options
are supported (with example values):

    iface: eth0
    token: cloudflare-api-token
    domains:
      - example.com
      - "*.example.com"
    # === optional fields
    # --- mode
    stack: ipv6
    # --- UI
    log-level: info
    # --- logic
    priority-subnets:
      - 2001:db8::/32
      - 2001:db8:1::/48
    multihost: true
    host-id: homelab-node-1
    # --- DNS record details
    proxy: enabled
    ttl: 180
    # --- daemon mode
    run-every: 10m
    state-file: /tmp/cfddns-eth0.state

Environment variables
--------------------------------------------------------------------------------

The configuration options can be specified as environment variables. To make an
environment variable name, prefix a flag name with CFDDNS_, replace dashes with
underscores, and convert to uppercase. List values are specified as a single
string containing elements separated by spaces.
For example:

    CFDDNS_CONFIG=/path/to/config.yaml
    CFDDNS_IFACE=eth0
    CFDDNS_TOKEN=cloudflare-api-token
    CFDDNS_DOMAINS='example.com *.example.com'
    CFDDNS_STACK=ipv6
    CFDDNS_LOG_LEVEL=info
    CFDDNS_PRIORITY_SUBNETS='2001:db8::/32 2001:db8:1::/48'
    CFDDNS_MULTIHOST=true
    CFDDNS_HOST_ID=homelab-node-1
    CFDDNS_PROXY=enabled
    CFDDNS_TTL=180
    CFDDNS_RUN_EVERY=10m
    CFDDNS_STATE_FILE=/tmp/cfddns-eth0.state

Usage:
  cloudflare-dynamic-dns [flags]

Flags:
      --config string              config file (default is $HOME/.cloudflare-dynamic-dns.yaml)
      --domains strings            Domain names to assign the address to.
  -h, --help                       help for cloudflare-dynamic-dns
      --host-id string             Unique host identifier. Must be specified in multihost mode.
                                   Must be a valid DNS label. It is stored in the Cloudflare DNS comments field in
                                   the format: "host-id (managed by cloudflare-dynamic-dns)"
      --iface string               Network interface to look up for an address.
      --log-level string           Sets logging level: trace, debug, info, warning, error, fatal, panic. (default "info")
      --multihost                  Enable multihost mode.
                                   In this mode it is possible to assign multiple addresses to a single domain.
                                   For correct operation, this mode must be enabled on all participating hosts and
                                   different host-ids must be specified for each host (see --host-id option).
      --priority-subnets strings   Subnets to prefer over others.
                                   If multiple addresses are found on the interface,
                                   the one from the subnet with the highest priority is used.
      --proxy string               Override proxy setting for created or updated DNS records.
                                   If set to "auto", preserves the current state of an updated record.
                                   Allowed values: "enabled", "disabled", "auto". (default "auto")
      --run-every string           Re-run the program every N duration until it's killed.
                                   The format is described at https://pkg.go.dev/time#ParseDuration.
                                   The minimum duration is 1m. Examples: 4h30m15s, 5m.
      --stack string               IP stack version: ipv4 or ipv6 (default "ipv6")
      --state-file string          Enables usage of a state file.
                                   In this mode, the previously used address is preserved
                                   between runs to avoid unnecessary calls to Cloudflare API.
                                   Automatically selects where to store the state file if no
                                   value is specified. See the State file section in usage.
      --token string               Cloudflare API token with DNS edit access rights.
      --ttl int                    Time to live, in seconds, of the DNS record.
                                   Must be between 60 and 86400, or 1 for 'automatic'. (default 1)
  -v, --version                    version for cloudflare-dynamic-dns

Installation

AUR

There are two packages in AUR ( 1, 2 ), that can be used on Arch-based distros:

yay -S cloudflare-dynamic-dns
# OR
yay -S cloudflare-dynamic-dns-bin

Docker

See the container registry page for details.

docker pull ghcr.io/zebradil/cloudflare-dynamic-dns:latest

DEB, RPM, APK

See the latest release page for the full list of packages.

Manual

Download the archive for your OS from the releases page.

Or get the source code and build the binary:

git clone https://github.com/Zebradil/cloudflare-dynamic-dns.git
# OR
curl -sL https://github.com/Zebradil/cloudflare-dynamic-dns/archive/refs/heads/master.tar.gz | tar xz

cd cloudflare-dynamic-dns-master
go build -o cloudflare-dynamic-dns main.go

Now you can run cloudflare-dynamic-dns manually (see Usage section).

If you want to do some automation with systemd, cloudflare-dynamic-dns has to be installed system-wide (it is possible to run systemd timer and service without root privileges, but I do not provide ready-to-use configuration for this yet):

sudo install -Dm755 cloudflare-dynamic-dns -t /usr/bin
sudo install -Dm644 systemd/* -t /usr/lib/systemd/system
sudo install -m700 -d /etc/cloudflare-dynamic-dns/config.d

Usage examples

Run manually

  1. Follow the steps from the Installation section.
  2. Run ./cloudflare-dynamic-dns --domains 'example.com,*.example.com' --iface eth0 --token cloudflare-api-token
    • NOTE: instead of compiling cloudflare-dynamic-dns binary, it can be replaced with go run main.go in the command above.

Instead of specifying command line arguments, it is possible to create ~/.cloudflare-dynamic-dns.yaml with the following structure:

iface: eth0
token: cloudflare-api-token
domains:
  - example.com
  - "*.example.com"

And then run ./cloudflare-dynamic-dns (or go run main.go) without arguments. Or put the configuration in any place and specify it with --config flag:

./cloudflare-dynamic-dns --config /any/place/config.yaml

Run in a Docker container

For the binary, the usage is the same as for the manual run. For the Docker container, we need to mount the configuration file into the container and provide access to the network stack of the host machine:

docker run --rm \
  --volume="/any/place/config.yaml:/config.yaml" \
  --network=host \
  ghcr.io/zebradil/cloudflare-dynamic-dns:latest \
    --config=/config.yaml

To run the program in daemon mode, add --run-every flag (and --detach if you want to run it in the background):

docker run --rm \
  --volume="/any/place/config.yaml:/config.yaml" \
  --network=host \
  --detach \
  ghcr.io/zebradil/cloudflare-dynamic-dns:latest \
    --config=/config.yaml \
    --run-every=5m

Systemd service and timer

It is possible to run cloudflare-dynamic-dns periodically via systemd. This requires privileged access to the system. Make sure that required systemd files are installed (see Installation section for details).

# 1. Create configuration file `/etc/cloudflare-dynamic-dns/config.d/<name>.yaml`
#    For example (I use "example.com" as <name>, replace the values according to your needs):
sudo tee -a /etc/cloudflare-dynamic-dns/config.d/example.com.yaml <<EOF
iface: eth0
token: cloudflare-api-token
domains:
  - example.com
  - "*.example.com"
EOF

# 3. Enable systemd timer
sudo systemctl enable --now [email protected]

This way (via running multiple timers) you can use multiple configurations at the same time.

By default, a timer is triggered one minute after boot and then every 5 minutes. It is not configurable currently.

State files are used to avoid unnecessary requests to Cloudflare API. They're created in /var/lib/cloudflare-dynamic-dns/ and named using configuration variables in corresponding config files (iface and a hash of domains). A state file contains an address which was set in a Cloudflare DNS AAAA or A record during the last successful run. If the current address is the same as the one in the state file, no additional API requests are done.

Development

Builds and releases are done with goreleaser.

There are several ways to build the application:

# Build with go for the current platform
go build -o cloudflare-dynamic-dns main.go

# Build with GoReleaser for all configured platforms
task go:build

# Use Docker
docker build -t cloudflare-dynamic-dns -f dev.Dockerfile .

Check the Taskfile.yml for more details.

GeReleaser

⚠️ Do not change .goreleaser.yml manually, do changes in .goreleaser.ytt.yml and run task misc:build:goreleaser-config instead (requires ytt installed).

Documentation

The usage section is generated by the update_readme script. For convenience, task docs:update-readme can be used to run it.

License

MIT

Disclaimer

This project is not affiliated with Cloudflare.

cloudflare-dynamic-dns's People

Contributors

dependabot[bot] avatar renovate[bot] avatar zebradil avatar zebradil-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

cloudflare-dynamic-dns's Issues

ZoneID Error

After the 4.2.1 (as well as 4.2.2) cloudflare-ddns throws the following error:

FATA[0047] Couldn't get ZoneID   error="ListZonesContext command failed: HTTP request failed: Get \"https://api.cloudflare.com/client/v4/zones?name=mydomain.tld&per_page=50\": dial tcp: lookup api.cloudflare.com on 100.100.100.100:53: read udp 100.76.145.12:52005->100.100.100.100:53: i/o timeout"

The yaml config file contains:

  • iface: eth0
  • token: #### (with zone access)
  • proxy: enabled
  • domains: (list of subdomains)
  • host-id: hostid

Support for TTL customization

The default TTL value is fixed at 60 seconds. It would be great if users can customize the TTL value, for example, 1 (for automatic).

Allow multiple domains

Change configuration format in the following way:

  iface: eth0
  token: cloudflare-api-token
- domain: example.com
+ domains:
+ - example.com
+ - *.example.com

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

dockerfile
Dockerfile
  • alpine 20240606
dev.Dockerfile
  • golang 1.22
github-actions
.github/workflows/flow-pr-meta.yml
  • amannn/action-semantic-pull-request v5
  • marocchino/sticky-pull-request-comment v2
  • marocchino/sticky-pull-request-comment v2
.github/workflows/flow-release.yml
  • actions/checkout v4
  • crazy-max/ghaction-import-gpg v6
  • cycjimmy/semantic-release-action v3
  • goreleaser/goreleaser-action v6
  • docker/setup-qemu-action v3
  • docker/setup-buildx-action v3
  • docker/login-action v3
.github/workflows/job-lint.yml
  • actions/setup-go v5
  • jaxxstorm/action-install-gh-release v1.12.0
  • actions/checkout v4
  • golangci/golangci-lint-action v6
.github/workflows/job-test.yml
  • actions/checkout v4
  • actions/setup-go v5
  • goreleaser/goreleaser-action v6
gomod
go.mod
  • go 1.19
  • github.com/cloudflare/cloudflare-go v0.100.0
  • github.com/spf13/cobra v1.8.1
  • github.com/spf13/viper v1.19.0
  • github.com/sirupsen/logrus v1.9.3

  • Check this box to trigger a request for Renovate to run again on this repository

Ignore SLAAC privacy addresses?

I've recently started using cloudflare-dynamic-dns to publish my server's IPv6 address, and it's working very well with one caveat.

On any modern OS using SLAAC, the privacy extension will be turned on by default, which causes short lived random addresses to be generated for the interface to be used to anonymize outgoing traffic. These addresses are not practically usable as the IP behind our DNS names because they change all the time. Rather, we want to register the stable EUI-64 address based on the MAC address.

Today, the tool's default behaviour causes it to pick any assigned address at random, as they are all equally acceptable for the current logic - they have the same subnet prefix, etc.

To make it pick the EUI-64 address, I had to use a /72 priority subnet prefix that incorporates the first part of the stable EUI-64 address. This works, but it now means I've had to hard-code the /64 that my ISP assigned me, and if that changes, my configuration would need to be manually changed as well.

Ideally, the tool would be able to prioritize the EUI-64 address (by matching against the interface MAC address) automatically.

Thanks!

Clean up outdated AAAA records

There are three cases currently:

  1. there are no AAAA records → a new record is created,
  2. there is one AAAA record → the record is updated (IPv6, TTL, etc.),
  3. there are several AAAA records → error, not implemented.

The last case is to be implemented.
The proposal:

  • if one of the records has IP address the same as the desired IP → remove all other records, update the matching record (TTL, etc.),
  • if no records have IP addresses matching to the desired IP → create a new record with the desired IP, delete the other records (this order of actions ensures that at any given moment there is a DNS record for the domain, though, not sure if this is important).

client registering wrong ipv6 address

Not sure if this is my fault or a code issue, my interface (enp0s4) has 3 v6 addresses, an fe80: address, a public address starting with 2600:, and an fdeb: address. Is there a way to tell it to ignore the fdeb address, because it's registering that in my zone instead of the 2600 address.

Thanks

Release MIPS Linux builds for OpenWrt

Hello,

Could you release MIPS Linux builds for use in OpenWrt routers and other embedded devices? It is very easy to compile a Go application for MIPS Linux.

Thank you.

Implement multiple domains

In the config file place a list of iface, token, domain, for example.
Or specify several config files (sounds better).

Records are not updated in the daemon mode when optional parameters are changed

In the daemon mode, CDD uses the state file to decide where to continue with processing or not. It exits earlier when the IP in the state file matches the currently selected IP. Apart from the IP, the state file name can change. It is composed of: the interface name, the IP stack (ipv4 or ipv6) and the hash from the domain names.

h := fnv.New64a()
domainHash := h.Sum([]byte(strings.Join(domains, " ")))
stateFilepath = fmt.Sprintf("%s_%s_%x", iface, stack, domainHash)

According to this logic, changes to other configuration values like proxy or ttl do not trigger re-processing. The workaround is to delete the state file.

This can be solved by including more information in state files. A state file can be named after the configuration file used to run CDD and can contain a bigger subset of the configuration serialized (instead of only IP).

Proxy status ON/OFF

When running the command in a terminal, all affected domains/subdomains' proxy status is set to off DNS only. Is there a flag that can set the proxy status to on Proxied for every domain/subdomain?

Ignore ULA addresses as part of an update

Hi there,

Recently I enabled ULA on my network and noticed that the program was updating my public DNS with ULA entries;

mikew@XXX:~/temp $ ./cloudflare-dynamic-dns
INFO[0000] Using config file:/home/mikew/.cloudflare-dynamic-dns.yaml
INFO[0000] Setting log level to:info
INFO[0000] Configuration                                 domains="[xxx]" iface=wlan0 prioritySubnets="[]" stateFilepath= systemd=false token="[40 characters]" ttl=1
INFO[0000] Found 3 public IPv6 addresses, selected fd8a:e1e4:16f9:179e:b10a:d4b1:6342:25c8  addresses="[fd8a:e1e4:16f9:179e:b10a:d4b1:6342:25c8 2604:3d08:xxxx::11c0 2604:3d08:xxxx:fe36]"

I updated to 2.1.1 and made use of the PrioritySubnets feature to simply include my ISPs entire v6 allocation so the problem is "solved", but I wonder if it's worthwhile to exclude anything that is not publicly routable when updating public DNS.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.