Skip to content

Commit 7b3b660

Browse files
authored
feat(provider): support deSEC (#496)
1 parent 6f75aa1 commit 7b3b660

File tree

5 files changed

+191
-0
lines changed

5 files changed

+191
-0
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
3838
- Cloudflare
3939
- DD24
4040
- DDNSS.de
41+
- deSEC
4142
- DigitalOcean
4243
- DonDominio
4344
- DNSOMatic
@@ -164,6 +165,7 @@ Check the documentation for your DNS provider:
164165
- [Aliyun](https://github.com/qdm12/ddns-updater/blob/master/docs/aliyun.md)
165166
- [Cloudflare](https://github.com/qdm12/ddns-updater/blob/master/docs/cloudflare.md)
166167
- [DDNSS.de](https://github.com/qdm12/ddns-updater/blob/master/docs/ddnss.de.md)
168+
- [deSEC](https://github.com/qdm12/ddns-updater/blob/master/docs/desec.md)
167169
- [DigitalOcean](https://github.com/qdm12/ddns-updater/blob/master/docs/digitalocean.md)
168170
- [DD24](https://github.com/qdm12/ddns-updater/blob/master/docs/domaindiscount24.md)
169171
- [DonDominio](https://github.com/qdm12/ddns-updater/blob/master/docs/dondominio.md)

docs/desec.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# deSEC
2+
3+
## Configuration
4+
5+
### Example
6+
7+
```json
8+
{
9+
"settings": [
10+
{
11+
"provider": "desec",
12+
"domain": "dedyn.io",
13+
"host": "host",
14+
"token": "token",
15+
"ip_version": "ipv4",
16+
"provider_ip": false
17+
}
18+
]
19+
}
20+
```
21+
22+
### Compulsory parameters
23+
24+
- `"domain"`
25+
- `"host"`
26+
- `"token"` is your token that you can create [here](https://desec.io/tokens)
27+
28+
### Optional parameters
29+
30+
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
31+
- `"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.
32+
33+
## Domain setup
34+
35+
[desec.io/domains](https://desec.io/domains)

internal/provider/constants/providers.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const (
99
Cloudflare models.Provider = "cloudflare"
1010
Dd24 models.Provider = "dd24"
1111
DdnssDe models.Provider = "ddnss"
12+
DeSEC models.Provider = "desec"
1213
DigitalOcean models.Provider = "digitalocean"
1314
DNSOMatic models.Provider = "dnsomatic"
1415
DNSPod models.Provider = "dnspod"
@@ -52,6 +53,7 @@ func ProviderChoices() []models.Provider {
5253
Cloudflare,
5354
Dd24,
5455
DdnssDe,
56+
DeSEC,
5557
DigitalOcean,
5658
DNSOMatic,
5759
DNSPod,

internal/provider/provider.go

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/qdm12/ddns-updater/internal/provider/providers/cloudflare"
1616
"github.com/qdm12/ddns-updater/internal/provider/providers/dd24"
1717
"github.com/qdm12/ddns-updater/internal/provider/providers/ddnss"
18+
"github.com/qdm12/ddns-updater/internal/provider/providers/desec"
1819
"github.com/qdm12/ddns-updater/internal/provider/providers/digitalocean"
1920
"github.com/qdm12/ddns-updater/internal/provider/providers/dnsomatic"
2021
"github.com/qdm12/ddns-updater/internal/provider/providers/dnspod"
@@ -79,6 +80,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, host string
7980
return dd24.New(data, domain, host, ipVersion)
8081
case constants.DdnssDe:
8182
return ddnss.New(data, domain, host, ipVersion)
83+
case constants.DeSEC:
84+
return desec.New(data, domain, host, ipVersion)
8285
case constants.DigitalOcean:
8386
return digitalocean.New(data, domain, host, ipVersion)
8487
case constants.DNSOMatic:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package desec
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/netip"
10+
"net/url"
11+
"strings"
12+
13+
"github.com/qdm12/ddns-updater/internal/models"
14+
"github.com/qdm12/ddns-updater/internal/provider/constants"
15+
"github.com/qdm12/ddns-updater/internal/provider/errors"
16+
"github.com/qdm12/ddns-updater/internal/provider/headers"
17+
"github.com/qdm12/ddns-updater/internal/provider/utils"
18+
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
19+
)
20+
21+
type Provider struct {
22+
domain string
23+
host string
24+
ipVersion ipversion.IPVersion
25+
token string
26+
useProviderIP bool
27+
}
28+
29+
func New(data json.RawMessage, domain, host string,
30+
ipVersion ipversion.IPVersion) (p *Provider, err error) {
31+
extraSettings := struct {
32+
Token string `json:"token"`
33+
UseProviderIP bool `json:"provider_ip"`
34+
}{}
35+
err = json.Unmarshal(data, &extraSettings)
36+
if err != nil {
37+
return nil, err
38+
}
39+
p = &Provider{
40+
domain: domain,
41+
host: host,
42+
ipVersion: ipVersion,
43+
token: extraSettings.Token,
44+
useProviderIP: extraSettings.UseProviderIP,
45+
}
46+
err = p.isValid()
47+
if err != nil {
48+
return nil, err
49+
}
50+
return p, nil
51+
}
52+
53+
func (p *Provider) isValid() error {
54+
switch {
55+
case p.token == "":
56+
return fmt.Errorf("%w", errors.ErrTokenNotSet)
57+
case p.host == "*":
58+
return fmt.Errorf("%w", errors.ErrHostWildcard)
59+
}
60+
return nil
61+
}
62+
63+
func (p *Provider) String() string {
64+
return fmt.Sprintf("[domain: %s | host: %s | provider: deSEC]", p.domain, p.host)
65+
}
66+
67+
func (p *Provider) Domain() string {
68+
return p.domain
69+
}
70+
71+
func (p *Provider) Host() string {
72+
return p.host
73+
}
74+
75+
func (p *Provider) IPVersion() ipversion.IPVersion {
76+
return p.ipVersion
77+
}
78+
79+
func (p *Provider) Proxied() bool {
80+
return false
81+
}
82+
83+
func (p *Provider) BuildDomainName() string {
84+
return utils.BuildDomainName(p.host, p.domain)
85+
}
86+
87+
func (p *Provider) HTML() models.HTMLRow {
88+
return models.HTMLRow{
89+
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
90+
Host: p.Host(),
91+
Provider: "<a href=\"https://desec.io/\">deSEC</a>",
92+
IPVersion: p.ipVersion.String(),
93+
}
94+
}
95+
96+
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
97+
u := url.URL{
98+
Scheme: "https",
99+
User: url.UserPassword(p.BuildDomainName(), p.token),
100+
Host: "update.dedyn.io",
101+
Path: "/nic/update",
102+
}
103+
values := url.Values{}
104+
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
105+
if !p.useProviderIP {
106+
values.Set("myip", ip.String())
107+
}
108+
u.RawQuery = values.Encode()
109+
110+
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
111+
if err != nil {
112+
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
113+
}
114+
headers.SetUserAgent(request)
115+
116+
response, err := client.Do(request)
117+
if err != nil {
118+
return netip.Addr{}, err
119+
}
120+
defer response.Body.Close()
121+
122+
b, err := io.ReadAll(response.Body)
123+
if err != nil {
124+
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
125+
}
126+
s := string(b)
127+
128+
switch response.StatusCode {
129+
case http.StatusOK:
130+
case http.StatusUnauthorized:
131+
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrAuth, utils.ToSingleLine(s))
132+
case http.StatusNotFound:
133+
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrHostnameNotExists, utils.ToSingleLine(s))
134+
default:
135+
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrHTTPStatusNotValid,
136+
response.StatusCode, utils.ToSingleLine(s))
137+
}
138+
139+
switch {
140+
case strings.HasPrefix(s, constants.Notfqdn):
141+
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
142+
case strings.HasPrefix(s, "badrequest"):
143+
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
144+
case strings.HasPrefix(s, "good"):
145+
return ip, nil
146+
default:
147+
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
148+
}
149+
}

0 commit comments

Comments
 (0)