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

GoIP.de support #623

Merged
merged 24 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5d4f3b9
Have GoIP.de mostly working, just need to finish implementing error m…
CyberAustin Feb 2, 2024
9ed3041
Ready to merge, or at least so I think
CyberAustin Feb 2, 2024
431c31e
Fixed or adjusted most comments. Ready for another review.
CyberAustin Feb 2, 2024
99e6602
Fixed some linting errors and changed subdomain to FQDN to make more …
CyberAustin Feb 2, 2024
8f3a58e
Have GoIP.de mostly working, just need to finish implementing error m…
CyberAustin Feb 2, 2024
eacc306
Ready to merge, or at least so I think
CyberAustin Feb 2, 2024
e627729
Fixed or adjusted most comments. Ready for another review.
CyberAustin Feb 2, 2024
2b32f01
Fixed some linting errors and changed subdomain to FQDN to make more …
CyberAustin Feb 2, 2024
f693740
Update goip.md
CyberAustin Feb 2, 2024
6a33a96
Update goip.md
CyberAustin Feb 2, 2024
639c713
Update goip.md
CyberAustin Feb 2, 2024
34b38d6
Update goip.md
CyberAustin Feb 2, 2024
7f9ee8d
Made more changes, per the PR.
CyberAustin Feb 3, 2024
1487340
Merge branch 'goip.de' of https://github.com/CyberAustin/ddns-updater…
CyberAustin Feb 3, 2024
34eca3c
Fixed Markdown, maybe
CyberAustin Feb 3, 2024
1ea6007
More lint
CyberAustin Feb 3, 2024
f06448d
Fix typo providor_ip -> provider_ip
qdm12 Feb 3, 2024
8638bb8
Use host as intended
qdm12 Feb 3, 2024
4ae33af
Use ErrBadRequest instead of ErrParametersNotValid and add "(access d…
qdm12 Feb 3, 2024
d0bed8d
Handle IPv6 addresses correctly
qdm12 Feb 3, 2024
77db29e
Always send IPv6 address query parameter (provider limitation)
qdm12 Feb 3, 2024
bafe1a3
Fix provider name in docs/goip.md
qdm12 Feb 3, 2024
b5c7a42
Review error processing
qdm12 Feb 3, 2024
214e52d
Fix subdomain query parameter key
qdm12 Feb 3, 2024
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 @@ -52,6 +52,7 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- Gandi
- GCP
- GoDaddy
- GoIP.de
- He.net
- Hetzner
- Infomaniak
Expand Down Expand Up @@ -189,6 +190,7 @@ Check the documentation for your DNS provider:
- [Gandi](docs/gandi.md)
- [GCP](docs/gcp.md)
- [GoDaddy](docs/godaddy.md)
- [GoIP.de](docs/goip.md)
- [He.net](docs/he.net.md)
- [Infomaniak](docs/infomaniak.md)
- [INWX](docs/inwx.md)
Expand Down
36 changes: 36 additions & 0 deletions docs/goip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# GoIP.de

## Configuration

### Example

```json
{
"settings": [
{
"provider": "goip",
"domain": "goip.de",
"host": "mysubdomain",
"username": "username",
"password": "password",
"provider_ip": true,
"ip_version": "",
"ipv6_suffix": ""

}
]
}
```

### Compulsory parameters

- `"host"` is the full FQDN of your ddns address. sample.goip.de or something.goip.it
- `"username"` is your goip.de username listed under "Routers"
- `"password"` is your router account password

### Optional parameters

- `"domain"` is the domain name which can be `goip.de` or `goip.it`, and defaults to `goip.de` if left unset.
- `"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. This is automatically disabled for an IPv6 public address since it is not supported.
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
2 changes: 2 additions & 0 deletions internal/provider/constants/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
Gandi models.Provider = "gandi"
GCP models.Provider = "gcp"
GoDaddy models.Provider = "godaddy"
GoIP models.Provider = "goip"
HE models.Provider = "he"
Hetzner models.Provider = "hetzner"
Infomaniak models.Provider = "infomaniak"
Expand Down Expand Up @@ -71,6 +72,7 @@ func ProviderChoices() []models.Provider {
Gandi,
GCP,
GoDaddy,
GoIP,
HE,
Hetzner,
Infomaniak,
Expand Down
1 change: 1 addition & 0 deletions internal/provider/errors/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
ErrEmailNotSet = errors.New("email is not set")
ErrEmailNotValid = errors.New("email address is not valid")
ErrGCPProjectNotSet = errors.New("GCP project is not set")
ErrDomainNotValid = errors.New("domain is not valid")
ErrHostOnlySubdomain = errors.New("host can only be a subdomain")
ErrHostWildcard = errors.New(`host cannot be a "*"`)
ErrIPv4KeyNotSet = errors.New("IPv4 key is not set")
Expand Down
3 changes: 3 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/qdm12/ddns-updater/internal/provider/providers/gandi"
"github.com/qdm12/ddns-updater/internal/provider/providers/gcp"
"github.com/qdm12/ddns-updater/internal/provider/providers/godaddy"
"github.com/qdm12/ddns-updater/internal/provider/providers/goip"
"github.com/qdm12/ddns-updater/internal/provider/providers/he"
"github.com/qdm12/ddns-updater/internal/provider/providers/hetzner"
"github.com/qdm12/ddns-updater/internal/provider/providers/infomaniak"
Expand Down Expand Up @@ -116,6 +117,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, host string
return gcp.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.GoDaddy:
return godaddy.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.GoIP:
return goip.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.HE:
return he.New(data, domain, host, ipVersion, ipv6Suffix)
case constants.Hetzner:
Expand Down
171 changes: 171 additions & 0 deletions internal/provider/providers/goip/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package goip

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

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

type Provider struct {
domain string
CyberAustin marked this conversation as resolved.
Show resolved Hide resolved
host string
ipVersion ipversion.IPVersion
ipv6Suffix netip.Prefix
username string
password string
useProviderIP bool
}

const defaultDomain = "goip.de"

func New(data json.RawMessage, domain, host string,
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
p *Provider, err error) {
if domain == "" {
domain = defaultDomain
}

extraSettings := struct {
Username string `json:"username"`
Password string `json:"password"`
UseProviderIP bool `json:"provider_ip"`
}{}
err = json.Unmarshal(data, &extraSettings)
if err != nil {
return nil, err
}
p = &Provider{
domain: domain,
host: host,
ipVersion: ipVersion,
ipv6Suffix: ipv6Suffix,
username: extraSettings.Username,
password: extraSettings.Password,
useProviderIP: extraSettings.UseProviderIP,
}
err = p.isValid()
if err != nil {
return nil, err
}
return p, nil
}

func (p *Provider) isValid() error {
switch {
CyberAustin marked this conversation as resolved.
Show resolved Hide resolved
case p.username == "":
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
case p.password == "":
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
case p.domain != defaultDomain && p.domain != "goip.it":
return fmt.Errorf(`%w: %q must be "goip.de" or "goip.it"`,
errors.ErrDomainNotValid, p.domain)
case p.host == "@" || p.host == "*":
return fmt.Errorf("%w: host %q is not valid", errors.ErrHostOnlySubdomain, p.host)
}
CyberAustin marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

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

func (p *Provider) Domain() string {
return p.domain
CyberAustin marked this conversation as resolved.
Show resolved Hide resolved
}

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

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

func (p *Provider) IPv6Suffix() netip.Prefix {
return p.ipv6Suffix
}

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: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
Host: p.Host(),
Provider: "<a href=\"https://www." + p.domain + "/\">" + p.domain + "</a>",
IPVersion: p.ipVersion.String(),
}
}

// See https://www.goip.de/update-url.html
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
u := url.URL{
Scheme: "https",
User: url.UserPassword(p.username, p.password),
Host: "www.goip.de",
Path: "/setip",
}
values := url.Values{}
values.Set("subdomain", p.BuildDomainName())
values.Set("username", p.username)
values.Set("password", p.password)
values.Set("shortResponse", "true")
if ip.Is4() {
if !p.useProviderIP {
values.Set("ip", ip.String())
}
} else {
// IPv6 cannot be automatically detected
values.Set("ip6", ip.String())
}
u.RawQuery = values.Encode()

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

response, err := client.Do(request)
if err != nil {
return netip.Addr{}, fmt.Errorf("doing http request: %w", err)
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode,
utils.BodyToSingleLine(response.Body))
}

b, err := io.ReadAll(response.Body)
if err != nil {
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
}
s := string(b)
switch {
case strings.HasPrefix(s, p.BuildDomainName()+" ("+ip.String()+")"):
return ip, nil
case strings.HasPrefix(strings.ToLower(s), "zugriff verweigert"):
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
default:
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
}
}
Loading