Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Dynu.com #285

Merged
merged 9 commits into from
Feb 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- Dreamhost
- DuckDNS
- DynDNS
- Dynu
- FreeDNS
- Gandi
- GoDaddy
Expand Down Expand Up @@ -164,6 +165,7 @@ Check the documentation for your DNS provider:
- [Dreamhost](https://github.com/qdm12/ddns-updater/blob/master/docs/dreamhost.md)
- [DuckDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/duckdns.md)
- [DynDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/dyndns.md)
- [Dynu](https://github.com/qdm12/ddns-updater/blob/master/docs/dynu.md)
- [DynV6](https://github.com/qdm12/ddns-updater/blob/master/docs/dynv6.md)
- [FreeDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/freedns.md)
- [Gandi](https://github.com/qdm12/ddns-updater/blob/master/docs/gandi.md)
Expand Down
37 changes: 37 additions & 0 deletions docs/dynu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Dynu

## Configuration

### Example

```json
{
"settings": [
{
"provider": "dynu",
"domain": "domain.com",
"host": "@",
"group": "group",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
}
]
}
```

### Compulsory parameters

- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"username"`
- `"password"` could be plain text or password in MD5 or SHA256 format (There's also an option for setting a password for IP Update only)

### Optional parameters

- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"group"` specify the Group for which you want to set the IP (will update any domains and subdomains in the same group)

## Domain setup
2 changes: 2 additions & 0 deletions internal/settings/constants/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
Dreamhost models.Provider = "dreamhost"
DuckDNS models.Provider = "duckdns"
Dyn models.Provider = "dyn"
Dynu models.Provider = "dynu"
DynV6 models.Provider = "dynv6"
FreeDNS models.Provider = "freedns"
Gandi models.Provider = "gandi"
Expand Down Expand Up @@ -50,6 +51,7 @@ func ProviderChoices() []models.Provider {
Dreamhost,
DuckDNS,
Dyn,
Dynu,
DynV6,
FreeDNS,
Gandi,
Expand Down
1 change: 1 addition & 0 deletions internal/settings/errors/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var (
ErrEmptyTTL = errors.New("TTL is not set")
ErrEmptyUsername = errors.New("empty username")
ErrEmptyZoneIdentifier = errors.New("empty zone identifier")
ErrEmptyHost = errors.New("host cannot be empty")
ErrHostOnlyAt = errors.New(`host can only be "@"`)
ErrHostOnlySubdomain = errors.New("host can only be a subdomain")
ErrHostWildcard = errors.New(`host cannot be a "*"`)
Expand Down
169 changes: 169 additions & 0 deletions internal/settings/providers/dynu/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package dynu

import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"

"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/settings/constants"
"github.com/qdm12/ddns-updater/internal/settings/errors"
"github.com/qdm12/ddns-updater/internal/settings/headers"
"github.com/qdm12/ddns-updater/internal/settings/utils"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)

type provider struct {
domain string
host string
group string
ipVersion ipversion.IPVersion
username string
password string
useProviderIP bool
}

func New(data json.RawMessage, domain, host string,
ipVersion ipversion.IPVersion) (p *provider, err error) {
extraSettings := struct {
Username string `json:"username"`
Password string `json:"password"`
UseProviderIP bool `json:"provider_ip"`
Group string `json:"group"`
}{}
if err := json.Unmarshal(data, &extraSettings); err != nil {
return nil, err
}

if host == "" {
host = "@" // default
}

p = &provider{
msxdan marked this conversation as resolved.
Show resolved Hide resolved
domain: domain,
host: host,
ipVersion: ipVersion,
group: extraSettings.Group,
username: extraSettings.Username,
password: extraSettings.Password,
useProviderIP: extraSettings.UseProviderIP,
}
if err := p.isValid(); err != nil {
return nil, err
}
return p, nil
}

func (p *provider) isValid() error {
switch {
case p.username == "":
return errors.ErrEmptyUsername
case p.password == "":
return errors.ErrEmptyPassword
case p.host == "*":
return errors.ErrHostWildcard
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcards are not supported then right? Are custom hosts (i.e. a.example.com) supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use @ or * it updates all DDNS IPs for my domains on Dynu, * doesn't update subdomains of a concrete domain, so *.domain.com updates domain.com but not subdomain.domain.com

There's an alias (subdomain) parameter that could be used to update a concrete subdomain, to update a bunch of domains and subdomains in one call there's an optional parameter called location which could be used if configured in Dynu, would be similar to * but you have to set your domains and subdomains to a group in dynu.

I'm adding more features to support all these parameters, I'll update the pull as soon as I finish.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use @ or * it updates all DDNS IPs for my domains on Dynu, * doesn't update subdomains of a concrete domain, so *.domain.com updates domain.com but not subdomain.domain.com

I'm a bit confused here.
Correct me if I'm wrong:

  • @ for domain.com updates only domain.com but not `sub.domain.com
  • * for domain.com updates domain.com, domain2.com but not sub.domain.com

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, @ does nothing really, if you don't set the hostname or if it's not a valid hostname for your user it will update all records for your domains that don't have a group assigned.

* doesn't acts like a wildcard, you can use it in the hostname but it will update only the domain.com and not sub.domain.com

}
return nil
}

func (p *provider) String() string {
return utils.ToString(p.domain, p.host, constants.Dynu, p.ipVersion)
}

func (p *provider) Domain() string {
return p.domain
}

func (p *provider) Host() string {
return p.host
}

func (p *provider) IPVersion() ipversion.IPVersion {
return p.ipVersion
}

func (p *provider) Proxied() bool {
return false
}

func (p *provider) BuildDomainName() string {
return utils.BuildDomainName(p.host, p.domain)
}

func (p *provider) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName())),
Host: models.HTML(p.Host()),
Provider: "<a href=\"https://dynu.com/\">Dynu</a>",
IPVersion: models.HTML(p.ipVersion.String()),
}
}

func (p *provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
u := url.URL{
Scheme: "https",
Host: "api.dynu.com",
Path: "/nic/update",
}
values := url.Values{}
values.Set("username", p.username)
values.Set("password", p.password)
values.Set("location", p.group)
if p.host != "@" {
values.Set("alias", p.host)
values.Set("hostname", p.domain)
} else {
values.Set("hostname", p.domain)
}
if !p.useProviderIP {
if ip.To4() == nil {
values.Set("myipv6", ip.String())
} else {
values.Set("myip", ip.String())
}
}
u.RawQuery = values.Encode()

request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, fmt.Errorf("%w: %s", errors.ErrBadRequest, err)
}
headers.SetUserAgent(request)

response, err := client.Do(request)
if err != nil {
return nil, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, err)
}
defer response.Body.Close()

b, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("%w: %s", errors.ErrUnmarshalResponse, err)
}
s := string(b)

if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %d: %s",
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
}

switch {
case strings.Contains(s, constants.Badauth):
return nil, errors.ErrAuth
case strings.Contains(s, constants.Notfqdn):
return nil, errors.ErrHostnameNotExists
case strings.Contains(s, constants.Abuse):
return nil, errors.ErrAbuse
case strings.Contains(s, "good"):
return ip, nil
case strings.Contains(s, "nochg"): // Updated but not changed
return ip, nil
default:
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
}
}
3 changes: 3 additions & 0 deletions internal/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/qdm12/ddns-updater/internal/settings/providers/dreamhost"
"github.com/qdm12/ddns-updater/internal/settings/providers/duckdns"
"github.com/qdm12/ddns-updater/internal/settings/providers/dyn"
"github.com/qdm12/ddns-updater/internal/settings/providers/dynu"
"github.com/qdm12/ddns-updater/internal/settings/providers/dynv6"
"github.com/qdm12/ddns-updater/internal/settings/providers/freedns"
"github.com/qdm12/ddns-updater/internal/settings/providers/gandi"
Expand Down Expand Up @@ -85,6 +86,8 @@ func New(provider models.Provider, data json.RawMessage, domain, host string,
return duckdns.New(data, domain, host, ipVersion, matcher)
case constants.Dyn:
return dyn.New(data, domain, host, ipVersion)
case constants.Dynu:
return dynu.New(data, domain, host, ipVersion)
case constants.DynV6:
return dynv6.New(data, domain, host, ipVersion)
case constants.FreeDNS:
Expand Down