Skip to content

Commit 03154c3

Browse files
authored
feat(providers): add myaddr.tools (#885)
1 parent be66792 commit 03154c3

File tree

6 files changed

+181
-1
lines changed

6 files changed

+181
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ This readme and the [docs/](docs/) directory are **versioned** to match the prog
7878
- Linode
7979
- Loopia
8080
- LuaDNS
81+
- Myaddr
8182
- Name.com
8283
- Namecheap
8384
- Netcup
@@ -243,6 +244,7 @@ Check the documentation for your DNS provider:
243244
- [Linode](docs/linode.md)
244245
- [Loopia](docs/loopia.md)
245246
- [LuaDNS](docs/luadns.md)
247+
- [Myaddr](docs/myaddr.md)
246248
- [Name.com](docs/name.com.md)
247249
- [Namecheap](docs/namecheap.md)
248250
- [Netcup](docs/netcup.md)

docs/myaddr.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# [Myaddr](https://myaddr.tools/)
2+
3+
## Configuration
4+
5+
### Example
6+
7+
```json
8+
{
9+
"settings": [
10+
{
11+
"provider": "myaddr",
12+
"domain": "your-name.myaddr.tools",
13+
"key": "key",
14+
"ip_version": "ipv4",
15+
"ipv6_suffix": ""
16+
}
17+
]
18+
}
19+
```
20+
21+
### Compulsory parameters
22+
23+
- `"domain"` - the **single** domain to update; note the `key` below updates all records and subdomains for this domain. It should be `your-name*.myaddr.tools`.
24+
- `"key"` - the private key corresponding to the domain to update
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
32+
33+
Claim a subdomain at [myaddr.tools](https://myaddr.tools/)

internal/params/json.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/qdm12/ddns-updater/internal/models"
1414
"github.com/qdm12/ddns-updater/internal/provider"
15+
"github.com/qdm12/ddns-updater/internal/provider/constants"
1516
"github.com/qdm12/ddns-updater/internal/provider/utils"
1617
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
1718
"golang.org/x/net/publicsuffix"
@@ -145,7 +146,10 @@ func extractAllSettings(jsonBytes []byte) (
145146
return allProviders, warnings, nil
146147
}
147148

148-
var ErrProviderNoLongerSupported = errors.New("provider no longer supported")
149+
var (
150+
ErrProviderNoLongerSupported = errors.New("provider no longer supported")
151+
ErrProviderMultipleDomains = errors.New("provider does not support multiple domains")
152+
)
149153

150154
func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
151155
retroGlobalIPv6Suffix netip.Prefix) (
@@ -178,6 +182,11 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
178182
}
179183
}
180184

185+
if common.Provider == string(constants.Myaddr) && len(owners) > 1 {
186+
return nil, nil, fmt.Errorf("%w: %s for parent domain %q",
187+
ErrProviderMultipleDomains, common.Provider, domain)
188+
}
189+
181190
if common.IPVersion == "" {
182191
common.IPVersion = ipversion.IP4or6.String()
183192
}

internal/provider/constants/providers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
Linode models.Provider = "linode"
3838
Loopia models.Provider = "loopia"
3939
LuaDNS models.Provider = "luadns"
40+
Myaddr models.Provider = "myaddr"
4041
Namecheap models.Provider = "namecheap"
4142
NameCom models.Provider = "name.com"
4243
Netcup models.Provider = "netcup"
@@ -90,6 +91,7 @@ func ProviderChoices() []models.Provider {
9091
Linode,
9192
Loopia,
9293
LuaDNS,
94+
Myaddr,
9395
Namecheap,
9496
NameCom,
9597
Njalla,

internal/provider/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"github.com/qdm12/ddns-updater/internal/provider/providers/linode"
4444
"github.com/qdm12/ddns-updater/internal/provider/providers/loopia"
4545
"github.com/qdm12/ddns-updater/internal/provider/providers/luadns"
46+
"github.com/qdm12/ddns-updater/internal/provider/providers/myaddr"
4647
"github.com/qdm12/ddns-updater/internal/provider/providers/namecheap"
4748
"github.com/qdm12/ddns-updater/internal/provider/providers/namecom"
4849
"github.com/qdm12/ddns-updater/internal/provider/providers/netcup"
@@ -148,6 +149,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, owner strin
148149
return loopia.New(data, domain, owner, ipVersion, ipv6Suffix)
149150
case constants.LuaDNS:
150151
return luadns.New(data, domain, owner, ipVersion, ipv6Suffix)
152+
case constants.Myaddr:
153+
return myaddr.New(data, domain, owner, ipVersion, ipv6Suffix)
151154
case constants.Namecheap:
152155
return namecheap.New(data, domain, owner)
153156
case constants.NameCom:
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package myaddr
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/netip"
9+
"net/url"
10+
"strings"
11+
12+
"github.com/qdm12/ddns-updater/internal/models"
13+
"github.com/qdm12/ddns-updater/internal/provider/constants"
14+
"github.com/qdm12/ddns-updater/internal/provider/errors"
15+
"github.com/qdm12/ddns-updater/internal/provider/headers"
16+
"github.com/qdm12/ddns-updater/internal/provider/utils"
17+
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
18+
)
19+
20+
type Provider struct {
21+
domain string
22+
owner string
23+
ipVersion ipversion.IPVersion
24+
ipv6Suffix netip.Prefix
25+
key string
26+
}
27+
28+
func New(data json.RawMessage, domain, owner string,
29+
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix,
30+
) (*Provider, error) {
31+
var providerSpecificSettings struct {
32+
Key string `json:"key"`
33+
}
34+
err := json.Unmarshal(data, &providerSpecificSettings)
35+
if err != nil {
36+
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
37+
}
38+
err = validateSettings(domain, providerSpecificSettings.Key)
39+
if err != nil {
40+
return nil, fmt.Errorf("validating provider specific settings: %w", err)
41+
}
42+
return &Provider{
43+
domain: domain,
44+
owner: owner,
45+
ipVersion: ipVersion,
46+
ipv6Suffix: ipv6Suffix,
47+
key: providerSpecificSettings.Key,
48+
}, nil
49+
}
50+
51+
func validateSettings(domain, key string) (err error) {
52+
err = utils.CheckDomain(domain)
53+
if err != nil {
54+
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
55+
}
56+
if key == "" {
57+
return fmt.Errorf("%w", errors.ErrKeyNotSet)
58+
}
59+
return nil
60+
}
61+
62+
func (p *Provider) String() string {
63+
return utils.ToString(p.Domain(), p.Owner(), constants.Myaddr, p.IPVersion())
64+
}
65+
66+
func (p *Provider) Domain() string {
67+
return p.domain
68+
}
69+
70+
func (p *Provider) Owner() string {
71+
return p.owner
72+
}
73+
74+
func (p *Provider) BuildDomainName() string {
75+
return utils.BuildDomainName(p.owner, p.domain)
76+
}
77+
78+
func (p *Provider) HTML() models.HTMLRow {
79+
return models.HTMLRow{
80+
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
81+
Owner: p.Owner(),
82+
Provider: "<a href=\"https://myaddr.tools/\">myaddr</a>",
83+
IPVersion: p.IPVersion().String(),
84+
}
85+
}
86+
87+
func (p *Provider) Proxied() bool {
88+
return false
89+
}
90+
91+
func (p *Provider) IPVersion() ipversion.IPVersion {
92+
return p.ipVersion
93+
}
94+
95+
func (p *Provider) IPv6Suffix() netip.Prefix {
96+
return p.ipv6Suffix
97+
}
98+
99+
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (netip.Addr, error) {
100+
u := &url.URL{
101+
Scheme: "https",
102+
Host: "myaddr.tools",
103+
Path: "/update",
104+
}
105+
v := url.Values{}
106+
v.Set("key", p.key)
107+
v.Set("ip", ip.String())
108+
buffer := strings.NewReader(v.Encode())
109+
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
110+
if err != nil {
111+
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
112+
}
113+
headers.SetContentType(request, "application/x-www-form-urlencoded")
114+
headers.SetUserAgent(request)
115+
response, err := client.Do(request)
116+
if err != nil {
117+
return netip.Addr{}, err
118+
}
119+
defer response.Body.Close()
120+
switch response.StatusCode {
121+
case http.StatusOK:
122+
return ip, nil
123+
case http.StatusBadRequest:
124+
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrBadRequest, utils.BodyToSingleLine(response.Body))
125+
case http.StatusNotFound:
126+
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrKeyNotValid, utils.BodyToSingleLine(response.Body))
127+
default:
128+
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
129+
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.BodyToSingleLine(response.Body))
130+
}
131+
}

0 commit comments

Comments
 (0)