Skip to content

Commit 18fba12

Browse files
committed
Add IPv64 provider
1 parent 20ac110 commit 18fba12

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ Check the documentation for your DNS provider:
242242
- [Infomaniak](docs/infomaniak.md)
243243
- [INWX](docs/inwx.md)
244244
- [Ionos](docs/ionos.md)
245+
- [IPv64](docs/ipv64.md)
245246
- [Linode](docs/linode.md)
246247
- [Loopia](docs/loopia.md)
247248
- [LuaDNS](docs/luadns.md)

docs/ipv64.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# IPv64
2+
3+
## Configuration
4+
5+
### Example
6+
7+
```json
8+
{
9+
"settings": [
10+
{
11+
"provider": "ipv64",
12+
"domain": "domain.com",
13+
"key": "key",
14+
"ip_version": "ipv4",
15+
"ipv6_suffix": ""
16+
}
17+
]
18+
}
19+
```
20+
21+
### Compulsory parameters
22+
23+
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
24+
- `"key"` that you can obtain [here](https://ipv64.net/account)
25+
26+
### Optional parameters
27+
28+
- `"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 or ipv6`.
29+
- `"ipv6_suffix"` is the IPv6 interface identifier suffix 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.
30+
31+
## Domain setup

internal/provider/constants/providers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
Infomaniak models.Provider = "infomaniak"
3535
INWX models.Provider = "inwx"
3636
Ionos models.Provider = "ionos"
37+
IPv64 models.Provider = "ipv64"
3738
Linode models.Provider = "linode"
3839
Loopia models.Provider = "loopia"
3940
LuaDNS models.Provider = "luadns"
@@ -89,6 +90,7 @@ func ProviderChoices() []models.Provider {
8990
Infomaniak,
9091
INWX,
9192
Ionos,
93+
IPv64,
9294
Linode,
9395
Loopia,
9496
LuaDNS,

internal/provider/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/qdm12/ddns-updater/internal/provider/providers/infomaniak"
4141
"github.com/qdm12/ddns-updater/internal/provider/providers/inwx"
4242
"github.com/qdm12/ddns-updater/internal/provider/providers/ionos"
43+
"github.com/qdm12/ddns-updater/internal/provider/providers/ipv64"
4344
"github.com/qdm12/ddns-updater/internal/provider/providers/linode"
4445
"github.com/qdm12/ddns-updater/internal/provider/providers/loopia"
4546
"github.com/qdm12/ddns-updater/internal/provider/providers/luadns"
@@ -144,6 +145,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, owner strin
144145
return inwx.New(data, domain, owner, ipVersion, ipv6Suffix)
145146
case constants.Ionos:
146147
return ionos.New(data, domain, owner, ipVersion, ipv6Suffix)
148+
case constants.IPv64:
149+
return ipv64.New(data, domain, owner, ipVersion, ipv6Suffix)
147150
case constants.Linode:
148151
return linode.New(data, domain, owner, ipVersion, ipv6Suffix)
149152
case constants.Loopia:
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package ipv64
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/netip"
9+
"net/url"
10+
11+
"github.com/qdm12/ddns-updater/internal/models"
12+
"github.com/qdm12/ddns-updater/internal/provider/constants"
13+
"github.com/qdm12/ddns-updater/internal/provider/errors"
14+
"github.com/qdm12/ddns-updater/internal/provider/headers"
15+
"github.com/qdm12/ddns-updater/internal/provider/utils"
16+
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
17+
)
18+
19+
type Provider struct {
20+
domain string
21+
owner string
22+
ipVersion ipversion.IPVersion
23+
ipv6Suffix netip.Prefix
24+
key string
25+
}
26+
27+
func New(data json.RawMessage, domain, owner string,
28+
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
29+
p *Provider, err error,
30+
) {
31+
extraSettings := struct {
32+
Key string `json:"key"`
33+
}{}
34+
err = json.Unmarshal(data, &extraSettings)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
err = validateSettings(domain, extraSettings.Key)
40+
if err != nil {
41+
return nil, fmt.Errorf("validating provider specific settings: %w", err)
42+
}
43+
44+
return &Provider{
45+
domain: domain,
46+
owner: owner,
47+
ipVersion: ipVersion,
48+
ipv6Suffix: ipv6Suffix,
49+
key: extraSettings.Key,
50+
}, nil
51+
}
52+
53+
func validateSettings(domain, token string) (err error) {
54+
err = utils.CheckDomain(domain)
55+
if err != nil {
56+
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
57+
}
58+
59+
if token == "" {
60+
return fmt.Errorf("%w", errors.ErrTokenNotSet)
61+
}
62+
return nil
63+
}
64+
65+
func (p *Provider) String() string {
66+
return utils.ToString(p.domain, p.owner, constants.IPv64, p.ipVersion)
67+
}
68+
69+
func (p *Provider) Domain() string {
70+
return p.domain
71+
}
72+
73+
func (p *Provider) Owner() string {
74+
return p.owner
75+
}
76+
77+
func (p *Provider) IPVersion() ipversion.IPVersion {
78+
return p.ipVersion
79+
}
80+
81+
func (p *Provider) IPv6Suffix() netip.Prefix {
82+
return p.ipv6Suffix
83+
}
84+
85+
func (p *Provider) Proxied() bool {
86+
return false
87+
}
88+
89+
func (p *Provider) BuildDomainName() string {
90+
return utils.BuildDomainName(p.owner, p.domain)
91+
}
92+
93+
func (p *Provider) HTML() models.HTMLRow {
94+
return models.HTMLRow{
95+
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
96+
Owner: p.Owner(),
97+
Provider: "<a href=\"https://ipv64.net/\">IPv64 DNS</a>",
98+
IPVersion: p.ipVersion.String(),
99+
}
100+
}
101+
102+
// see https://ipv64.net/dyndns_updater_api
103+
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
104+
u := url.URL{
105+
Scheme: "https",
106+
Host: "ipv64.net",
107+
Path: "/nic/update",
108+
}
109+
110+
values := url.Values{}
111+
values.Set("key", p.key)
112+
values.Set("domain", utils.BuildURLQueryHostname(p.owner, p.domain))
113+
114+
if ip.Is4() {
115+
values.Set("ip", ip.String())
116+
} else {
117+
values.Set("ip6", ip.String())
118+
}
119+
120+
u.RawQuery = values.Encode()
121+
122+
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
123+
if err != nil {
124+
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
125+
}
126+
headers.SetUserAgent(request)
127+
128+
response, err := client.Do(request)
129+
if err != nil {
130+
return netip.Addr{}, err
131+
}
132+
defer response.Body.Close()
133+
134+
if response.StatusCode == http.StatusOK {
135+
return ip, nil
136+
}
137+
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
138+
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.BodyToSingleLine(response.Body))
139+
}

0 commit comments

Comments
 (0)