• Stars
    star
    114
  • Rank 306,623 (Top 7 %)
  • Language
    Rust
  • License
    MIT License
  • Created over 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A dynamic dns client

dness

Finesse with dness: a dynamic dns client

ci


Motivation

When one has a server that is subjected to unpredictable IP address changes, such as at home or elsewhere, a change in IP address causes unexpected downtime. Instead of paying for a static IP address, one can employ a dynamic dns client on said server, which will update the WAN IP address on the dns server.

There are plenty of dynamic dns clients, including the venerable ddclient, but troublesome installation + perl system dependency resolution, and cache format errors have left much to be desired. Other solutions fall short, so dness was created with the following goals:

Features:

  • ✔ Cross platform (Windows, Mac, Linux, ARM, BSD)
  • ✔ Zero dependencies (one can opt to dynamically link openssl when compiling from source)
  • ✔ A standard configuration (TOML) that is similar to ddclient's
  • ✔ Support for multiple Dynamic DNS Services:
  • ✔ Permissively licensed

Installation

To maximize initial flexibility, dness is not a daemon. Instead it relies on the host's scheduling (cron, systemd timers, windows scheduler).

Ubuntu / Debian (systemd + deb)

dpkg -i dness<version>_amd64.deb

# ensure it is working
/usr/bin/dness

# enable systemd timer
systemctl daemon-reload
systemctl start dness.timer
systemctl enable dness.timer

# update configuration
${EDITOR:-vi} /etc/dness/dness.conf

# create the environment variables with sensitive info
(umask 077; touch /etc/dness/dness.env)
${EDITOR:-vi} /etc/dness/dness.env

Windows

  • Download the latest zip with "windows" in its name
  • Unzip
  • Create configuration file (dness.conf)
  • Execute dness.exe -c dness.conf to verify behavior
  • If desired, use windows task scheduler to execute command at specific times

Other

Download the latest appropriate target

Configuration

No configuration file is necessary when only the WAN IP is desired.

./dness

Sample output:

[INFO  trust_dns_proto::xfer::dns_exchange] sending message via: UDP(208.67.220.220:53)
[INFO  dness] resolved address to 256.256.256.256 in 23ms
[INFO  dness] processed all: (updated: 0, already current: 0, missing: 0) in 29ms

Simple Configuration

But dness can do more than resolve one's WAN IP. Below is a simple configuration file (conventionally named dness.conf) that will update cloudflare records.

[[domains]]
type = "cloudflare"
token = "dec0de"
zone = "example.com"
records = [
    "n.example.com"
]

Execute dness with the configuration:

./dness -c dness.conf

Substitute Sensitive Values

Dness will substitute in values from the environment into the configuration so that sensitive values don't need to be specified in the config:

[[domains]]
type = "cloudflare"
token = "{{MY_CLOUDFLARE_TOKEN}}"
zone = "example.com"
records = [
    "n.example.com"
]

This is a great way to run dness in an unprivileged account but still have access to sensitive values.

Annotated Configuration

Below are the configuration options, but they've been annotated with comments.

[log]
# How verbose the log is. Common values: Error, Warn, Info, Debug, Trace
# The default level is info
level = "Debug"

[[domains]]
# We denote that our domain is managed by cloudflare
type = "cloudflare"

# Create Cloudflare token by using the use "Edit zone DNS" API token template.
# Alternatively one can use email + key fields but the token is recommended as
# it is more secure
token = "dec0de"

# The email address registered in cloudflare that is authorized to update dns
# records. Only required when not using the token field
# email = "[email protected]"

# The cloudflare key can be found in the domain overview, in "Get your API key"
# and view "Global API Key". Required when "email" is used
# key = "deadbeef"

# The zone is the domain name
zone = "example.com"

# List of A records found under the DNS tab that should be updated
records = [
    "n.example.com"
]

# More than one domain can be specified in a config!
[[domains]]
type = "cloudflare"
email = "[email protected]"
key = "deadbeef"
zone = "example2.com"
records = [
    "n.example2.com",
    "n2.example2.com"
]

Supported Dynamic DNS Services

Cloudflare

[[domains]]
# We denote that our domain is managed by cloudflare
type = "cloudflare"

# The email address registered in cloudflare that is authorized to update dns
# records
email = "[email protected]"

# The cloudflare key can be found in the domain overview, in "Get your API key"
# and view "Global API Key" (or another key as appropriate)
key = "deadbeef"

# The zone is the domain name
zone = "example.com"

# List of A records found under the DNS tab that should be updated
records = [
    "n.example.com"
]

Cloudflare dynamic dns service works in three steps:

  1. Send GET to translate the zone (example.com) to cloudflare's id
  2. Send GET to find all the domains under the zone and their sub-ids
    • Cloudflare paginates the response to handle many subdomains
    • It is possible to query for individual domains but as long as more than one desired domain in each page -- this methods cuts down requests
  3. Each desired domain in the config is checked to ensure that it is set to our address. In this way cloudflare is our cache (to guard against nefarious users updating out of band)

GoDaddy

[[domains]]
# denote that the domain is managed by godaddy
type = "godaddy"

# The GoDaddy domain: https://dcc.godaddy.com/domains/
domain = "example.com"

# This is the api key, you can create one here:
# https://developer.godaddy.com/keys
key = "abc123"

# The password for the key, top secret!
secret = "ef"

# The records to update. "@" = "example.com", "a" = "a.example.com"
records = [ "@", "a" ]

GoDaddy dynamic dns service works as the following:

  1. Send a GET request to find all records in the domain
  2. Find all the expected records (and log those that are missing) and check their current IP
  3. Update the remote IP as needed, ensuring that original properties are preserved in the upload, so that we don't overwrite a property like TTL.

Namecheap

[[domains]]
# Namecheap requires that dynamic dns is enabled in their UI!
type = "namecheap"
domain = "test-dness-1.xyz"
ddns_password = "super_secret_password"

# The records to update. Make sure they are listed as A + Dynamic DNS
# "@" = "test-dness-1.xyz"
# "* = "<any-sub-domain>.test-dness-1.xyz"
# "sub = "sub.test-dness-1.xyz"
records = [ "@", "*", "sub" ]

The namecheap services requires dynamic dns enabled in their UI.

Updating the dns entry works as follows:

  • A dns query is sent to cloudflare to check the IP of the record
  • If the IP is different than WAN then a request is sent to namecheap to update it
  • If the IP is the same, no action is taken

This method suffers from natural flow of dns propagation. When namecheap receives the update, it may take up to an hour for cloudflare to see the new record. In the meantime, dness will keep updating namecheap servers with the WAN. This has no consequential side effects other than momentary confusion why updates are being sent to namecheap every 5 minutes. Future revisions of this provider may use another method (like API integration) if the current method proves deficient enough.

He.net

[[domains]]
type = "he"
hostname = "test-dness-1.xyz"
password = "super_secret_password"
records = [ "@", "sub" ]

he.net follows the same flow as Namecheap (check the current record via DNS and update if necessary).

No-IP

[[domains]]
type = "noip"
hostname = "dnesstest.hopto.org"
username = "[email protected]"
password = "super_secret_password"

Dynu

[[domains]]
type = "dynu"
hostname = "test-dness.camdvr.org"
username = "MyUserName"

# ip update password:
# https://www.dynu.com/en-US/ControlPanel/ManageCredentials
password = "IpUpdatePassword"

# The records to update.
# "@" = "test-dness.camdvr.org"
# "sub = "sub.test-dness.camdvr.org"
records = [ "@", "sub" ]

Porkbun

[[domains]]
# denote that the domain is managed by porkbun
type = "porkbun"

# The Porkbun domain: https://porkbun.com/account/domainsSpeedy
# IMPORTANT: You must enable API Access for the domain at the above url.
domain = "example.com"

# This is the api key, you can create one here:
# https://porkbun.com/account/api
key = "abc123"

# The password for the key, top secret! Only visible once when you create the key.
secret = "ef"

# The records to update. "@" = "example.com", "a" = "a.example.com" "*" = "*.example.com"
# Both "@" and "" are valid to configure root domain.
records = [ "@", "a" ]

Porkbun dynamic dns service works similar to GoDaddy:

  1. Send a POST request to find all records in the domain
  2. Find all the expected records (and log those that are missing) and check their current IP
  3. Update the remote IP as needed, ensuring that original properties are preserved in the upload, so that we don't overwrite a property like TTL.

Supported WAN IP Resolvers

There are a couple different methods for dness to resolve the WAN IP address.

OpenDNS

The default WAN IP address resolver queries OpenDNS. It resolves IPv4 addresses by querying "myip.opendns.com" against resolver1.opendns.com and resolver2.opendns.com.

While it is the default, it can explicitly be specified by appending snippet below to the top of the config:

ip_resolver = "opendns"

Ipify

OpenDNS may not be available to all networks, so one can configure dness to use Ipify. Instead of using DNS, an HTTPs request will be sent. To opt into using Ipify, append the snippet below to the top of the config:

ip_resolver = "ipify"

More Repositories

1

OhmGraphite

Expose hardware sensor data to Graphite / InfluxDB / Prometheus / Postgres / Timescaledb
C#
402
star
2

rrinlog

Replacing Elasticsearch with Rust and SQLite
Rust
185
star
3

highway-rs

Native Rust port of Google's HighwayHash, which makes use of SIMD instructions for a fast and strong hash function
Rust
146
star
4

Pfim

.NET Standard targa (.tga) and direct draw surface (.dds) image decoder
C#
107
star
5

boxcars

Rocket League Replay parser in Rust
Rust
99
star
6

jomini

Parses Paradox files into javascript objects
TypeScript
76
star
7

bitter

Extract bits from a byte slice
Rust
70
star
8

Farmhash.Sharp

Port of Google's farmhash algorithm to .NET
C#
62
star
9

bottle-ssl

A simple web page using BottlePy and SSL
Python
56
star
10

rrrocket

Rocket League Replay parser to JSON -- CLI app
Rust
53
star
11

Pdoxcl2Sharp

A Paradox Interactive general file parser
C#
39
star
12

highwayhasher

HighwayHash implementation for node and browsers
TypeScript
34
star
13

collectd-rust-plugin

Write a low-cost, ergonomic plugin for collectd
Rust
28
star
14

EU4.Savegame

Web application to generate statistics based on EU4 savegames
C#
13
star
15

pg-collectd

An alternative and opinionated postgres collectd writer
Rust
13
star
16

rl-web

Online Rocket League Replay Parser
TypeScript
12
star
17

Pfarah

Parses files generated by the Clausewitz engine
F#
7
star
18

eecs381-lint

Additional style checks for C/C++ for clang-tidy
C++
6
star
19

dropwizard-hikaricp-benchmark

Dropwizard with HikariCP DB Connection Pool Benchmarks
Java
6
star
20

next.js-wasm-worker

Bug repro
JavaScript
4
star
21

register

Digesting and Distilling Federal Register data
XQuery
4
star
22

HighPerformanceUnsafeCSharp

The many different ways to accomplish the same task, often using unsafe constructs.
C#
3
star
23

bench

Benchmarking Wasm for insight and troubleshooting
TypeScript
2
star
24

eu4save

Parses and provides methods for viewing an eu4 save
JavaScript
1
star
25

hhsum

Calculate checksums of files using HighwayHash
Rust
1
star
26

vigenere

Recover vigenere ciphertext using frequency analysis
TypeScript
1
star