mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:54:09 -04:00
chore(all): migrate from net.IP* to net/netip
This commit is contained in:
@@ -243,7 +243,7 @@ func _main(ctx context.Context, settingsSource SettingsSource, args []string, lo
|
||||
|
||||
updater := update.NewUpdater(db, client, notify, logger)
|
||||
runner := update.NewRunner(db, updater, ipGetter, config.Update.Period,
|
||||
config.IPv6.Mask, config.Update.Cooldown, logger, resolver, timeNow)
|
||||
config.IPv6.MaskBits, config.Update.Cooldown, logger, resolver, timeNow)
|
||||
|
||||
runnerHandler, runnerCtx, runnerDone := goshutdown.NewGoRoutineHandler("runner")
|
||||
go runner.Run(runnerCtx, runnerDone)
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"net"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
)
|
||||
|
||||
type IPv6 struct {
|
||||
Mask net.IPMask
|
||||
// MaskBits is the IPv6 mask in bits, for example 128 for /128
|
||||
MaskBits uint8
|
||||
}
|
||||
|
||||
func (i *IPv6) setDefaults() {
|
||||
const ipv6Bits = 8 * net.IPv6len
|
||||
if i.Mask == nil {
|
||||
i.Mask = net.CIDRMask(ipv6Bits, ipv6Bits)
|
||||
}
|
||||
i.MaskBits = gosettings.DefaultNumber(i.MaskBits,
|
||||
uint8(netip.IPv6Unspecified().BitLen()))
|
||||
}
|
||||
|
||||
func (i IPv6) mergeWith(other IPv6) (merged IPv6) {
|
||||
merged.Mask = gosettings.MergeWithSlice(i.Mask, other.Mask)
|
||||
merged.MaskBits = gosettings.MergeWithNumber(i.MaskBits, other.MaskBits)
|
||||
return merged
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMaskBitsTooHigh = errors.New("mask bits is too high")
|
||||
)
|
||||
|
||||
func (i IPv6) Validate() (err error) {
|
||||
const maxMaskBits = 128
|
||||
if i.MaskBits > maxMaskBits {
|
||||
return fmt.Errorf("%w: %d must be equal or below to %d",
|
||||
ErrMaskBitsTooHigh, i.MaskBits, maxMaskBits)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
37
internal/config/sources/env/ipv6.go
vendored
37
internal/config/sources/env/ipv6.go
vendored
@@ -3,7 +3,7 @@ package env
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/config/settings"
|
||||
@@ -15,7 +15,7 @@ func (s *Source) readIPv6() (settings settings.IPv6, err error) {
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
settings.Mask, err = ipv6DecimalPrefixToMask(maskStr)
|
||||
settings.MaskBits, err = ipv6DecimalPrefixToBits(maskStr)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("%w: for environment variable IPV6_PREFIX", err)
|
||||
}
|
||||
@@ -25,33 +25,20 @@ func (s *Source) readIPv6() (settings settings.IPv6, err error) {
|
||||
|
||||
var ErrParsePrefix = errors.New("cannot parse IP prefix")
|
||||
|
||||
func ipv6DecimalPrefixToMask(prefixDecimal string) (ipMask net.IPMask, err error) {
|
||||
if prefixDecimal == "" {
|
||||
return nil, fmt.Errorf("%w: empty prefix", ErrParsePrefix)
|
||||
}
|
||||
|
||||
func ipv6DecimalPrefixToBits(prefixDecimal string) (maskBits uint8, err error) {
|
||||
prefixDecimal = strings.TrimPrefix(prefixDecimal, "/")
|
||||
|
||||
const bits = 8 * net.IPv6len
|
||||
|
||||
ones, consumed, ok := decimalToInteger(prefixDecimal)
|
||||
if !ok || consumed != len(prefixDecimal) || ones < 0 || ones > bits {
|
||||
return nil, fmt.Errorf("%w: %s", ErrParsePrefix, prefixDecimal)
|
||||
const base, bits = 10, 8
|
||||
maskBitsUint64, err := strconv.ParseUint(prefixDecimal, base, bits)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing prefix decimal as uint8: %w", err)
|
||||
}
|
||||
|
||||
return net.CIDRMask(ones, bits), nil
|
||||
}
|
||||
|
||||
func decimalToInteger(s string) (ones int, i int, ok bool) {
|
||||
const big = 0xFFFFFF // Bigger than we need, not too big to worry about overflow
|
||||
const ten = 10
|
||||
|
||||
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
|
||||
ones = ones*ten + int(s[i]-'0')
|
||||
if ones >= big {
|
||||
return big, i, false
|
||||
}
|
||||
const maxBits = 128
|
||||
if bits > maxBits {
|
||||
return 0, fmt.Errorf("%w: %d bits cannot be larger than %d",
|
||||
ErrParsePrefix, bits, maxBits)
|
||||
}
|
||||
|
||||
return ones, i, true
|
||||
return uint8(maskBitsUint64), nil
|
||||
}
|
||||
|
||||
22
internal/config/sources/env/ipv6_test.go
vendored
22
internal/config/sources/env/ipv6_test.go
vendored
@@ -2,43 +2,43 @@ package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_ipv6DecimalPrefixToMask(t *testing.T) {
|
||||
func Test_ipv6DecimalPrefixToBits(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
prefixDecimal string
|
||||
ipMask net.IPMask
|
||||
maskBits uint8
|
||||
err error
|
||||
}{
|
||||
"empty": {
|
||||
err: fmt.Errorf("cannot parse IP prefix: empty prefix"),
|
||||
err: fmt.Errorf(`parsing prefix decimal as uint8: ` +
|
||||
`strconv.ParseUint: parsing "": invalid syntax`),
|
||||
},
|
||||
"malformed": {
|
||||
prefixDecimal: "malformed",
|
||||
err: fmt.Errorf("cannot parse IP prefix: malformed"),
|
||||
err: fmt.Errorf(`parsing prefix decimal as uint8: ` +
|
||||
`strconv.ParseUint: parsing "malformed": invalid syntax`),
|
||||
},
|
||||
"with leading slash": {
|
||||
prefixDecimal: "/78",
|
||||
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 0, 0, 0, 0, 0, 0},
|
||||
maskBits: 78,
|
||||
},
|
||||
"without leading slash": {
|
||||
prefixDecimal: "78",
|
||||
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 0, 0, 0, 0, 0, 0},
|
||||
maskBits: 78,
|
||||
},
|
||||
"full IPv6 mask": {
|
||||
prefixDecimal: "/128",
|
||||
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||
maskBits: 128,
|
||||
},
|
||||
"zero IPv6 mask": {
|
||||
prefixDecimal: "/0",
|
||||
ipMask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func Test_ipv6DecimalPrefixToMask(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipMask, err := ipv6DecimalPrefixToMask(testCase.prefixDecimal)
|
||||
ipMask, err := ipv6DecimalPrefixToBits(testCase.prefixDecimal)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
@@ -56,7 +56,7 @@ func Test_ipv6DecimalPrefixToMask(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.ipMask, ipMask)
|
||||
assert.Equal(t, testCase.maskBits, ipMask)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -9,6 +9,6 @@ import (
|
||||
|
||||
type PersistentDatabase interface {
|
||||
Close() error
|
||||
StoreNewIP(domain, host string, ip net.IP, t time.Time) (err error)
|
||||
StoreNewIP(domain, host string, ip netip.Addr, t time.Time) (err error)
|
||||
GetEvents(domain, host string) (events []models.HistoryEvent, err error)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/constants"
|
||||
@@ -30,23 +31,35 @@ func isHealthy(db AllSelecter, resolver LookupIPer) (err error) {
|
||||
} else if record.Settings.Proxied() {
|
||||
continue
|
||||
}
|
||||
|
||||
hostname := record.Settings.BuildDomainName()
|
||||
lookedUpIPs, err := resolver.LookupIP(context.Background(), "ip", hostname)
|
||||
|
||||
currentIP := record.History.GetCurrentIP()
|
||||
if !currentIP.IsValid() {
|
||||
return fmt.Errorf("%w: for hostname %s", ErrRecordIPNotSet, hostname)
|
||||
}
|
||||
|
||||
lookedUpNetIPs, err := resolver.LookupIP(context.Background(), "ip", hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentIP := record.History.GetCurrentIP()
|
||||
if currentIP == nil {
|
||||
return fmt.Errorf("%w: for hostname %s", ErrRecordIPNotSet, hostname)
|
||||
}
|
||||
|
||||
found := false
|
||||
lookedUpIPsString := make([]string, len(lookedUpIPs))
|
||||
for i, lookedUpIP := range lookedUpIPs {
|
||||
lookedUpIPsString[i] = lookedUpIP.String()
|
||||
if lookedUpIP.Equal(currentIP) {
|
||||
lookedUpIPsString := make([]string, len(lookedUpNetIPs))
|
||||
for i, netIP := range lookedUpNetIPs {
|
||||
var ip netip.Addr
|
||||
switch {
|
||||
case netIP == nil:
|
||||
case netIP.To4() != nil:
|
||||
ip = netip.AddrFrom4([4]byte(netIP.To4()))
|
||||
default: // IPv6
|
||||
ip = netip.AddrFrom16([16]byte(netIP.To16()))
|
||||
}
|
||||
if ip.Compare(currentIP) == 0 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
lookedUpIPsString[i] = ip.String()
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("%w: %s instead of %s for %s",
|
||||
|
||||
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -12,17 +12,17 @@ import (
|
||||
type History []HistoryEvent // current and previous ips
|
||||
|
||||
type HistoryEvent struct { // current and previous ips
|
||||
IP net.IP `json:"ip"`
|
||||
Time time.Time `json:"time"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
// GetPreviousIPs returns an antichronological list of previous
|
||||
// IP addresses if there is any.
|
||||
func (h History) GetPreviousIPs() []net.IP {
|
||||
func (h History) GetPreviousIPs() []netip.Addr {
|
||||
if len(h) <= 1 {
|
||||
return nil
|
||||
}
|
||||
IPs := make([]net.IP, len(h)-1)
|
||||
IPs := make([]netip.Addr, len(h)-1)
|
||||
const two = 2
|
||||
for i := len(h) - two; i >= 0; i-- {
|
||||
IPs[i] = h[i].IP
|
||||
@@ -31,9 +31,9 @@ func (h History) GetPreviousIPs() []net.IP {
|
||||
}
|
||||
|
||||
// GetCurrentIP returns the current IP address (latest in history).
|
||||
func (h History) GetCurrentIP() net.IP {
|
||||
func (h History) GetCurrentIP() netip.Addr {
|
||||
if len(h) < 1 {
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
}
|
||||
return h[len(h)-1].IP
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func (h History) GetDurationSinceSuccess(now time.Time) string {
|
||||
|
||||
func (h History) String() (s string) {
|
||||
currentIP := h.GetCurrentIP()
|
||||
if currentIP == nil {
|
||||
if !currentIP.IsValid() {
|
||||
return ""
|
||||
}
|
||||
successTime := h[len(h)-1].Time
|
||||
|
||||
@@ -96,7 +96,7 @@ func checkData(data dataModel) error {
|
||||
}
|
||||
t = event.Time
|
||||
switch {
|
||||
case event.IP == nil:
|
||||
case !event.IP.IsValid():
|
||||
return fmt.Errorf("%w: IP %d of %d for record %s",
|
||||
ErrIPEmpty, i+1, len(record.Events), record)
|
||||
case event.Time.IsZero():
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
)
|
||||
|
||||
// StoreNewIP stores a new IP address for a certain domain and host.
|
||||
func (db *Database) StoreNewIP(domain, host string, ip net.IP, t time.Time) (err error) {
|
||||
func (db *Database) StoreNewIP(domain, host string, ip netip.Addr, t time.Time) (err error) {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
for i, record := range db.data.Records {
|
||||
|
||||
@@ -28,7 +28,7 @@ func (r *Record) HTML(now time.Time) models.HTMLRow {
|
||||
time.Since(r.Time).Round(time.Second).String()+" ago"))
|
||||
}
|
||||
currentIP := r.History.GetCurrentIP()
|
||||
if currentIP != nil {
|
||||
if currentIP.IsValid() {
|
||||
row.CurrentIP = models.HTML(`<a href="https://ipinfo.io/"` + currentIP.String() + `\>` + currentIP.String() + "</a>")
|
||||
} else {
|
||||
row.CurrentIP = NotAvailable
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/settings/constants"
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
)
|
||||
|
||||
func (p *Provider) createRecord(ctx context.Context,
|
||||
client *http.Client, ip net.IP) (recordID string, err error) {
|
||||
client *http.Client, ip netip.Addr) (recordID string, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
"github.com/qdm12/ddns-updater/internal/settings/constants"
|
||||
@@ -96,10 +96,10 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
// Documentation at https://api.aliyun.com/
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -107,15 +107,15 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
if stderrors.Is(err, errors.ErrRecordNotFound) {
|
||||
recordID, err = p.createRecord(ctx, client, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
return newIP, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
return newIP, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
}
|
||||
|
||||
err = p.updateRecord(ctx, client, recordID, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
return newIP, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
|
||||
@@ -3,8 +3,8 @@ package aliyun
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/settings/constants"
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
recordID string, ip net.IP) (err error) {
|
||||
recordID string, ip netip.Addr) (err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -98,7 +98,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dyndns.kasserver.com",
|
||||
@@ -108,7 +108,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values := url.Values{}
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
if !p.useProviderIP {
|
||||
if ip.To4() == nil { // ipv6
|
||||
if ip.Is6() { // ipv6
|
||||
values.Set("myip6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
@@ -118,59 +118,60 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnsuccessfulResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnsuccessfulResponse, err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", 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))
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
case constants.Nineoneone:
|
||||
return nil, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
case constants.Abuse:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case "!donator":
|
||||
return nil, fmt.Errorf("%w", errors.ErrFeatureUnavailable)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrFeatureUnavailable)
|
||||
case constants.Badagent:
|
||||
return nil, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case constants.Nohost:
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
}
|
||||
if !strings.Contains(s, "nochg") && !strings.Contains(s, "good") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
var ips []net.IP
|
||||
if ip.To4() != nil {
|
||||
var ips []netip.Addr
|
||||
if ip.Is4() {
|
||||
ips = utils.FindIPv4Addresses(s)
|
||||
} else {
|
||||
ips = utils.FindIPv6Addresses(s)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -146,10 +146,10 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
|
||||
// Obtain domain ID.
|
||||
// See https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records.
|
||||
func (p *Provider) getRecordID(ctx context.Context, client *http.Client, newIP net.IP) (
|
||||
func (p *Provider) getRecordID(ctx context.Context, client *http.Client, newIP netip.Addr) (
|
||||
identifier string, upToDate bool, err error) {
|
||||
recordType := constants.A
|
||||
if newIP.To4() == nil {
|
||||
if newIP.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -214,10 +214,10 @@ func (p *Provider) getRecordID(ctx context.Context, client *http.Client, newIP n
|
||||
return listRecordsResponse.Result[0].ID, false, nil
|
||||
}
|
||||
|
||||
func (p *Provider) CreateRecord(ctx context.Context, client *http.Client, ip net.IP) (recordID string, err error) {
|
||||
func (p *Provider) CreateRecord(ctx context.Context, client *http.Client, ip netip.Addr) (recordID string, err error) {
|
||||
recordType := constants.A
|
||||
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -294,9 +294,9 @@ func (p *Provider) CreateRecord(ctx context.Context, client *http.Client, ip net
|
||||
return parsedJSON.Result.ID, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -306,10 +306,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
case stderrors.Is(err, errors.ErrNoResultReceived):
|
||||
identifier, err = p.CreateRecord(ctx, client, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
}
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
case upToDate:
|
||||
return ip, nil
|
||||
}
|
||||
@@ -338,24 +338,24 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
encoder := json.NewEncoder(buffer)
|
||||
err = encoder.Encode(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode > http.StatusUnsupportedMediaType {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&parsedJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
if !parsedJSON.Success {
|
||||
@@ -380,14 +380,15 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
for _, e := range parsedJSON.Errors {
|
||||
errStr += fmt.Sprintf("error %d: %s; ", e.Code, e.Message)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, errStr)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, errStr)
|
||||
}
|
||||
|
||||
newIP = net.ParseIP(parsedJSON.Result.Content)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, parsedJSON.Result.Content)
|
||||
} else if !newIP.Equal(ip) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err = netip.ParseAddr(parsedJSON.Result.Content)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if newIP.Compare(ip) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -91,7 +91,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
// see https://www.domaindiscount24.com/faq/en/dynamic-dns
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -110,24 +110,24 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
@@ -135,11 +135,11 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
switch {
|
||||
case strings.Contains(s, "authorization failed"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case s == "", strings.Contains(s, "success"):
|
||||
return ip, nil
|
||||
// TODO missing cases
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -101,7 +101,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "www.ddnss.de",
|
||||
@@ -113,7 +113,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
if !p.useProviderIP {
|
||||
ipKey := "ip"
|
||||
if p.dualStack && ip.To4() == nil { // ipv6 update for dual stack
|
||||
if p.dualStack && ip.Is6() { // ipv6 update for dual stack
|
||||
ipKey = "ip6"
|
||||
}
|
||||
values.Set(ipKey, ip.String())
|
||||
@@ -122,37 +122,37 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(s, "badysys"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrInvalidSystemParam)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrInvalidSystemParam)
|
||||
case strings.Contains(s, constants.Badauth):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.Contains(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.Contains(s, "Updated 1 hostname"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -142,15 +142,15 @@ func (p *Provider) getRecordID(ctx context.Context, recordType string, client *h
|
||||
return result.DomainRecords[0].ID, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil { // IPv6
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
recordID, err := p.getRecordID(ctx, recordType, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
@@ -172,23 +172,23 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = encoder.Encode(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -200,14 +200,14 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&responseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
newIP = net.ParseIP(responseData.DomainRecord.Data)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, responseData.DomainRecord.Data)
|
||||
} else if !newIP.Equal(ip) {
|
||||
return nil, fmt.Errorf("%w: sent %s but received %s ",
|
||||
newIP, err = netip.ParseAddr(responseData.DomainRecord.Data)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if newIP.Compare(ip) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -99,7 +99,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
// Multiple hosts can be updated in one query, see https://www.dnsomatic.com/docs/api
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -124,58 +124,59 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case constants.Nohost, constants.Notfqdn:
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case constants.Badagent:
|
||||
return nil, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
case constants.Abuse:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case "dnserr", constants.Nineoneone:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrDNSServerSide, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrDNSServerSide, s)
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "nochg") && !strings.Contains(s, "good") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
if ip.To4() != nil {
|
||||
var ips []netip.Addr
|
||||
if ip.Is4() {
|
||||
ips = utils.FindIPv4Addresses(s)
|
||||
} else {
|
||||
ips = utils.FindIPv6Addresses(s)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -93,9 +93,9 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
headers.SetUserAgent(request)
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
u := url.URL{
|
||||
@@ -116,18 +116,18 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -143,14 +143,14 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&recordResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
var recordID, recordLine string
|
||||
for _, record := range recordResp.Records {
|
||||
if record.Type == recordType && record.Name == p.host {
|
||||
receivedIP := net.ParseIP(record.Value)
|
||||
if ip.Equal(receivedIP) {
|
||||
receivedIP, err := netip.ParseAddr(record.Value)
|
||||
if err == nil && ip.Compare(receivedIP) == 0 {
|
||||
return ip, nil
|
||||
}
|
||||
recordID = record.ID
|
||||
@@ -159,7 +159,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
}
|
||||
if recordID == "" {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNotFound)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNotFound)
|
||||
}
|
||||
|
||||
u.Path = "/Record.Ddns"
|
||||
@@ -176,24 +176,24 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err = http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err = client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
var ddnsResp struct {
|
||||
@@ -205,15 +205,16 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = json.Unmarshal(data, &ddnsResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
ipStr := ddnsResp.Record.Value
|
||||
receivedIP := net.ParseIP(ipStr)
|
||||
if receivedIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s from JSON data: %s", errors.ErrIPReceivedMalformed, ipStr, data)
|
||||
} else if !ip.Equal(receivedIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, receivedIP.String())
|
||||
receivedIP, err := netip.ParseAddr(ipStr)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if ip.Compare(receivedIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -108,7 +108,7 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
headers.SetAccept(request, "application/json")
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "simple-api.dondominio.net",
|
||||
@@ -118,7 +118,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values.Set("apipasswd", p.password)
|
||||
values.Set("domain", p.domain)
|
||||
values.Set("name", p.name)
|
||||
isIPv4 := ip.To4() != nil
|
||||
isIPv4 := ip.Is4()
|
||||
if isIPv4 {
|
||||
values.Set("ipv4", ip.String())
|
||||
} else {
|
||||
@@ -129,18 +129,18 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -158,22 +158,23 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&responseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
if !responseData.Success {
|
||||
return nil, fmt.Errorf("%w: %s (error code %d)",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s (error code %d)",
|
||||
errors.ErrUnsuccessfulResponse, responseData.ErrorCodeMessage, responseData.ErrorCode)
|
||||
}
|
||||
ipString := responseData.ResponseData.GlueRecords[0].IPv4
|
||||
if !isIPv4 {
|
||||
ipString = responseData.ResponseData.GlueRecords[0].IPv6
|
||||
}
|
||||
newIP = net.ParseIP(ipString)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, ipString)
|
||||
} else if !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err = netip.ParseAddr(ipString)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
@@ -93,25 +93,25 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
records, err := p.getRecords(ctx, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrListRecords, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrListRecords, err)
|
||||
}
|
||||
|
||||
var oldIP net.IP
|
||||
var oldIP netip.Addr
|
||||
for _, data := range records.Data {
|
||||
if data.Type == recordType && data.Record == utils.BuildURLQueryHostname(p.host, p.domain) {
|
||||
if data.Editable == "0" {
|
||||
return nil, fmt.Errorf("%w", errors.ErrRecordNotEditable)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrRecordNotEditable)
|
||||
}
|
||||
oldIP = net.ParseIP(data.Value)
|
||||
if ip.Equal(oldIP) { // constants.Success, nothing to change
|
||||
oldIP, err = netip.ParseAddr(data.Value)
|
||||
if err == nil && ip.Compare(oldIP) == 0 { // constants.Success, nothing to change
|
||||
return ip, nil
|
||||
}
|
||||
break
|
||||
@@ -121,13 +121,13 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
// Create the record with the new IP before removing the old one if it exists.
|
||||
err = p.createRecord(ctx, client, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
}
|
||||
|
||||
if oldIP != nil { // Found editable record with a different IP address, so remove it
|
||||
if oldIP.IsValid() { // Found editable record with a different IP address, so remove it
|
||||
err = p.removeRecord(ctx, client, oldIP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRemoveRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRemoveRecord, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +203,9 @@ func (p *Provider) getRecords(ctx context.Context, client *http.Client) (
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (p *Provider) removeRecord(ctx context.Context, client *http.Client, ip net.IP) error { //nolint:dupl
|
||||
func (p *Provider) removeRecord(ctx context.Context, client *http.Client, ip netip.Addr) error { //nolint:dupl
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -251,9 +251,9 @@ func (p *Provider) removeRecord(ctx context.Context, client *http.Client, ip net
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) createRecord(ctx context.Context, client *http.Client, ip net.IP) error { //nolint:dupl
|
||||
func (p *Provider) createRecord(ctx context.Context, client *http.Client, ip netip.Addr) error { //nolint:dupl
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
@@ -94,7 +94,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "www.duckdns.org",
|
||||
@@ -106,7 +106,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values.Set("token", p.token)
|
||||
u.RawQuery = values.Encode()
|
||||
if !p.useProviderIP {
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
values.Set("ip6", ip.String())
|
||||
} else {
|
||||
values.Set("ip", ip.String())
|
||||
@@ -115,44 +115,45 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
const minChars = 2
|
||||
switch {
|
||||
case len(s) < minChars:
|
||||
return nil, fmt.Errorf("%w: response %q is too short", errors.ErrUnmarshalResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: response %q is too short", errors.ErrUnmarshalResponse, s)
|
||||
case s[0:minChars] == "KO":
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case s[0:minChars] == "OK":
|
||||
ips := utils.FindIPv4Addresses(s)
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !newIP.Equal(ip) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && newIP.Compare(ip) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -105,7 +105,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
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.clientKey),
|
||||
@@ -121,35 +121,35 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.HasPrefix(s, "badrequest"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(s, "good"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -106,7 +106,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "api.dynu.com",
|
||||
@@ -119,7 +119,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
hostname := utils.BuildDomainName(p.host, p.domain)
|
||||
values.Set("hostname", hostname)
|
||||
if !p.useProviderIP {
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
values.Set("myipv6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
@@ -129,39 +129,39 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnsuccessfulResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnsuccessfulResponse, err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(s, constants.Badauth):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.Contains(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.Contains(s, constants.Abuse):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", 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))
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -90,8 +90,8 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
isIPv4 := ip.To4() != nil
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
isIPv4 := ip.Is4()
|
||||
host := "dynv6.com"
|
||||
if isIPv4 {
|
||||
host = "ipv4." + host
|
||||
@@ -117,19 +117,19 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
return ip, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -87,9 +87,9 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
var hostPrefix string
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
hostPrefix = "v6."
|
||||
}
|
||||
|
||||
@@ -101,34 +101,34 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
loweredResponse := strings.ToLower(s)
|
||||
switch {
|
||||
case loweredResponse == "":
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
case strings.HasPrefix(loweredResponse, "no ip change detected"),
|
||||
strings.HasPrefix(loweredResponse, "updated "):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -96,14 +96,10 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
request.Header.Set("X-Api-Key", p.key)
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
var ipStr string
|
||||
if ip.To4() == nil { // IPv6
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
ipStr = ip.To16().String()
|
||||
} else {
|
||||
ipStr = ip.To4().String()
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
@@ -123,28 +119,28 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
Values [1]string `json:"rrset_values"`
|
||||
TTL int `json:"rrset_ttl"`
|
||||
}{
|
||||
Values: [1]string{ipStr},
|
||||
Values: [1]string{ip.Unmap().String()},
|
||||
TTL: ttl,
|
||||
}
|
||||
err = encoder.Encode(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/settings/constants"
|
||||
ddnserrors "github.com/qdm12/ddns-updater/internal/settings/errors"
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
option.WithCredentialsJSON(p.credentials),
|
||||
option.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating GCP DDNS service: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("creating GCP DDNS service: %w", err)
|
||||
}
|
||||
rrSetsService := clouddns.NewResourceRecordSetsService(ddnsService)
|
||||
|
||||
@@ -36,7 +36,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
if errors.Is(err, ddnserrors.ErrNotFound) {
|
||||
rrSetFound = false // not finding the record is fine
|
||||
} else {
|
||||
return nil, fmt.Errorf("getting record resource set: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("getting record resource set: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,14 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
if !rrSetFound {
|
||||
err = p.createRecord(rrSetsService, fqdn, recordType, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating record: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("creating record: %w", err)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
err = p.updateRecord(rrSetsService, fqdn, recordType, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updating record: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("updating record: %w", err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
@@ -79,7 +79,7 @@ func (p *Provider) getResourceRecordSet(rrSetsService *clouddns.ResourceRecordSe
|
||||
}
|
||||
|
||||
func (p *Provider) createRecord(rrSetsService *clouddns.ResourceRecordSetsService,
|
||||
fqdn, recordType string, ip net.IP) (err error) {
|
||||
fqdn, recordType string, ip netip.Addr) (err error) {
|
||||
rrSet := &clouddns.ResourceRecordSet{
|
||||
Name: fqdn,
|
||||
Rrdatas: []string{ip.String()},
|
||||
@@ -91,7 +91,7 @@ func (p *Provider) createRecord(rrSetsService *clouddns.ResourceRecordSetsServic
|
||||
}
|
||||
|
||||
func (p *Provider) updateRecord(rrSetsService *clouddns.ResourceRecordSetsService,
|
||||
fqdn, recordType string, ip net.IP) (err error) {
|
||||
fqdn, recordType string, ip netip.Addr) (err error) {
|
||||
rrSet := &clouddns.ResourceRecordSet{
|
||||
Name: fqdn,
|
||||
Rrdatas: []string{ip.String()},
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
@@ -103,9 +103,9 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
headers.SetAccept(request, "application/json")
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
type goDaddyPutBody struct {
|
||||
@@ -124,18 +124,18 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = encoder.Encode(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPut, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
@@ -145,7 +145,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
err = fmt.Errorf("%w: %d", errors.ErrBadHTTPStatus, response.StatusCode)
|
||||
@@ -154,7 +154,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
jsonErr := json.Unmarshal(b, &parsedJSON)
|
||||
if jsonErr != nil || parsedJSON.Message == "" {
|
||||
return nil, fmt.Errorf("%w: %s", err, utils.ToSingleLine(string(b)))
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", err, utils.ToSingleLine(string(b)))
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", err, parsedJSON.Message)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", err, parsedJSON.Message)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -96,7 +96,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "domains.google.com",
|
||||
@@ -113,57 +113,58 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
case constants.Nohost, constants.Notfqdn:
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case constants.Badagent:
|
||||
return nil, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
case constants.Abuse:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case constants.Nineoneone:
|
||||
return nil, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
case "conflict constants.A", "conflict constants.AAAA":
|
||||
return nil, fmt.Errorf("%w", errors.ErrConflictingRecord)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrConflictingRecord)
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "nochg") && !strings.Contains(s, "good") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
if ip.To4() != nil {
|
||||
var ips []netip.Addr
|
||||
if ip.Is4() {
|
||||
ips = utils.FindIPv4Addresses(s)
|
||||
} else {
|
||||
ips = utils.FindIPv6Addresses(s)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -90,7 +90,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
fqdn := p.BuildDomainName()
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -107,47 +107,48 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "nochg") && !strings.Contains(s, "good") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
if ip.To4() != nil {
|
||||
var ips []netip.Addr
|
||||
if ip.Is4() {
|
||||
ips = utils.FindIPv4Addresses(s)
|
||||
} else {
|
||||
ips = utils.FindIPv6Addresses(s)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -98,7 +98,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "infomaniak.com",
|
||||
@@ -114,19 +114,19 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
@@ -134,34 +134,36 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
case http.StatusOK:
|
||||
switch {
|
||||
case strings.HasPrefix(s, "good "):
|
||||
newIP = net.ParseIP(s[5:])
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, s)
|
||||
} else if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP)
|
||||
newIP, err = netip.ParseAddr(s[5:])
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
case strings.HasPrefix(s, "nochg "):
|
||||
newIP = net.ParseIP(s[6:])
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: in response %q", errors.ErrNoResultReceived, s)
|
||||
} else if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP)
|
||||
newIP, err = netip.ParseAddr(s[6:])
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: in response %q", errors.ErrNoResultReceived, s)
|
||||
} else if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
case http.StatusBadRequest:
|
||||
switch s {
|
||||
case constants.Nohost:
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -94,7 +94,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
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),
|
||||
@@ -103,7 +103,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
if ip.To4() != nil {
|
||||
if ip.Is4() {
|
||||
values.Set("myip", ip.String())
|
||||
} else {
|
||||
values.Set("myipv6", ip.String())
|
||||
@@ -113,28 +113,28 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating http request: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("doing http request: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("doing http request: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s, "good") || !strings.HasPrefix(s, "nochg") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
@@ -90,14 +90,14 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
|
||||
// Using https://www.linode.com/docs/api/domains/
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
domainID, err := p.getDomainID(ctx, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetDomainID, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetDomainID, err)
|
||||
}
|
||||
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
|
||||
@@ -105,16 +105,16 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
if goerrors.Is(err, errors.ErrNotFound) {
|
||||
err := p.createRecord(ctx, client, domainID, recordType, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
}
|
||||
return ip, nil
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetRecordID, err)
|
||||
}
|
||||
|
||||
err = p.updateRecord(ctx, client, domainID, recordID, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
@@ -242,7 +242,7 @@ func (p *Provider) getRecordID(ctx context.Context, client *http.Client,
|
||||
}
|
||||
|
||||
func (p *Provider) createRecord(ctx context.Context, client *http.Client,
|
||||
domainID int, recordType string, ip net.IP) (err error) {
|
||||
domainID int, recordType string, ip netip.Addr) (err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "api.linode.com",
|
||||
@@ -292,18 +292,19 @@ func (p *Provider) createRecord(ctx context.Context, client *http.Client,
|
||||
return fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
newIP := net.ParseIP(responseData.IP)
|
||||
if newIP == nil {
|
||||
return fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, responseData.IP)
|
||||
} else if !newIP.Equal(ip) {
|
||||
return fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err := netip.ParseAddr(responseData.IP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if newIP.Compare(ip) != 0 {
|
||||
return fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
domainID, recordID int, ip net.IP) (err error) {
|
||||
domainID, recordID int, ip netip.Addr) (err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "api.linode.com",
|
||||
@@ -347,11 +348,12 @@ func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
return fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
newIP := net.ParseIP(data.IP)
|
||||
if newIP == nil {
|
||||
return fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, data.IP)
|
||||
} else if !newIP.Equal(ip) {
|
||||
return fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err := netip.ParseAddr(data.IP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if newIP.Compare(ip) != 0 {
|
||||
return fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -99,22 +99,22 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
}
|
||||
|
||||
// Using https://www.luadns.com/api.html
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
zoneID, err := p.getZoneID(ctx, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetZoneID, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetZoneID, err)
|
||||
}
|
||||
|
||||
record, err := p.getRecord(ctx, client, zoneID, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrGetRecordInZone, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrGetRecordInZone, err)
|
||||
}
|
||||
|
||||
newRecord := record
|
||||
newRecord.Content = ip.String()
|
||||
err = p.updateRecord(ctx, client, zoneID, newRecord)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func (p *Provider) getZoneID(ctx context.Context, client *http.Client) (zoneID i
|
||||
return 0, fmt.Errorf("%w", errors.ErrZoneNotFound)
|
||||
}
|
||||
|
||||
func (p *Provider) getRecord(ctx context.Context, client *http.Client, zoneID int, ip net.IP) (
|
||||
func (p *Provider) getRecord(ctx context.Context, client *http.Client, zoneID int, ip netip.Addr) (
|
||||
record luaDNSRecord, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -226,7 +226,7 @@ func (p *Provider) getRecord(ctx context.Context, client *http.Client, zoneID in
|
||||
return record, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
recordName := utils.BuildURLQueryHostname(p.host, p.domain) + "."
|
||||
@@ -288,7 +288,8 @@ func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
}
|
||||
|
||||
if updatedRecord.Content != newRecord.Content {
|
||||
return fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, updatedRecord.Content)
|
||||
return fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, newRecord.Content, updatedRecord.Content)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
@@ -101,7 +101,7 @@ func (p *Provider) setHeaders(request *http.Request) {
|
||||
headers.SetAccept(request, "application/xml")
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dynamicdns.park-your-domain.com",
|
||||
@@ -118,18 +118,18 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
p.setHeaders(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -146,11 +146,11 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&parsedXML)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
if parsedXML.Errors.Error != "" {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, parsedXML.Errors.Error)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, parsedXML.Errors.Error)
|
||||
}
|
||||
|
||||
if parsedXML.IP == "" {
|
||||
@@ -159,12 +159,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
newIP = net.ParseIP(parsedXML.IP)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, parsedXML.IP)
|
||||
}
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err = netip.ParseAddr(parsedXML.IP)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -88,7 +88,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "njal.la",
|
||||
@@ -97,7 +97,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values := url.Values{}
|
||||
values.Set("h", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("k", p.key)
|
||||
updatingIP6 := ip.To4() == nil
|
||||
updatingIP6 := ip.Is6()
|
||||
if p.useProviderIP {
|
||||
values.Set("auto", "")
|
||||
} else {
|
||||
@@ -111,13 +111,13 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
@@ -131,30 +131,31 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
}
|
||||
err = decoder.Decode(&respBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
switch response.StatusCode {
|
||||
case http.StatusOK:
|
||||
if respBody.Message != "record updated" {
|
||||
return nil, fmt.Errorf("%w: message received: %s", errors.ErrUnknownResponse, respBody.Message)
|
||||
return netip.Addr{}, fmt.Errorf("%w: message received: %s", errors.ErrUnknownResponse, respBody.Message)
|
||||
}
|
||||
ipString := respBody.Value.A
|
||||
if updatingIP6 {
|
||||
ipString = respBody.Value.AAAA
|
||||
}
|
||||
newIP = net.ParseIP(ipString)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, ipString)
|
||||
} else if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
newIP, err = netip.ParseAddr(ipString)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
case http.StatusUnauthorized:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrAuth, respBody.Message)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrAuth, respBody.Message)
|
||||
case http.StatusInternalServerError:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrBadRequest, respBody.Message)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrBadRequest, respBody.Message)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, respBody.Message)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, respBody.Message)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -101,7 +101,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "dynupdate.no-ip.com",
|
||||
@@ -111,7 +111,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
if !p.useProviderIP {
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
values.Set("myipv6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
@@ -121,61 +121,62 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
switch s {
|
||||
case "":
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoResultReceived)
|
||||
case constants.Nineoneone:
|
||||
return nil, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
case constants.Abuse:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case "!donator":
|
||||
return nil, fmt.Errorf("%w", errors.ErrFeatureUnavailable)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrFeatureUnavailable)
|
||||
case constants.Badagent:
|
||||
return nil, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBannedUserAgent)
|
||||
case constants.Badauth:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case constants.Nohost:
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
}
|
||||
|
||||
if !strings.Contains(s, "nochg") && !strings.Contains(s, "good") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
if ip.To4() != nil {
|
||||
var ips []netip.Addr
|
||||
if ip.Is4() {
|
||||
ips = utils.FindIPv4Addresses(s)
|
||||
} else {
|
||||
ips = utils.FindIPv6Addresses(s)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoIPInResponse)
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !p.useProviderIP && !ip.Equal(newIP) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP.String())
|
||||
if !p.useProviderIP && ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return newIP, nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -97,7 +97,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
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),
|
||||
@@ -113,35 +113,36 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s, "good ") {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
responseIPString := strings.TrimPrefix(s, "good ")
|
||||
newIP = net.ParseIP(responseIPString)
|
||||
if newIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMalformed, responseIPString)
|
||||
} else if !p.useProviderIP && !newIP.Equal(ip) {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrIPReceivedMismatch, newIP)
|
||||
newIP, err = netip.ParseAddr(responseIPString)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrIPReceivedMalformed, err)
|
||||
} else if !p.useProviderIP && newIP.Compare(ip) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -135,7 +135,8 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) updateWithDynHost(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) updateWithDynHost(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),
|
||||
@@ -152,37 +153,37 @@ func (p *Provider) updateWithDynHost(ctx context.Context, client *http.Client, i
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrBadRequest, err)
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, s)
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.HasPrefix(s, "badrequest"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(s, "nochg"):
|
||||
return ip, nil
|
||||
case strings.HasPrefix(s, "good"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,15 +192,12 @@ var (
|
||||
ErrRefresh = stderrors.New("cannot refresh records")
|
||||
)
|
||||
|
||||
func (p *Provider) updateWithZoneDNS(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
newIP net.IP, err error) {
|
||||
func (p *Provider) updateWithZoneDNS(ctx context.Context, client *http.Client, ip netip.Addr) (
|
||||
newIP netip.Addr, err error) {
|
||||
ipStr := ip.Unmap().String()
|
||||
recordType := constants.A
|
||||
var ipStr string
|
||||
if ip.To4() == nil { // IPv6
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
ipStr = ip.To16().String()
|
||||
} else {
|
||||
ipStr = ip.To4().String()
|
||||
}
|
||||
// subDomain filter of the ovh api expect an empty string to get @ record
|
||||
subDomain := p.host
|
||||
@@ -209,37 +207,37 @@ func (p *Provider) updateWithZoneDNS(ctx context.Context, client *http.Client, i
|
||||
|
||||
timestamp, err := p.getAdjustedUnixTimestamp(ctx, client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrGetAdjustedTime, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", ErrGetAdjustedTime, err)
|
||||
}
|
||||
|
||||
recordIDs, err := p.getRecords(ctx, client, recordType, subDomain, timestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrListRecords, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrListRecords, err)
|
||||
}
|
||||
|
||||
if len(recordIDs) == 0 {
|
||||
err = p.createRecord(ctx, client, recordType, subDomain, ipStr, timestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrCreateRecord, err)
|
||||
}
|
||||
} else {
|
||||
for _, recordID := range recordIDs {
|
||||
err = p.updateRecord(ctx, client, recordID, ipStr, timestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUpdateRecord, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = p.refresh(ctx, client, timestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrRefresh, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", ErrRefresh, err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
if p.mode != "api" {
|
||||
return p.updateWithDynHost(ctx, client, ip)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
@@ -257,20 +257,20 @@ func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil { // IPv6
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
ipStr := ip.String()
|
||||
recordIDs, err := p.getRecordIDs(ctx, client, recordType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
if len(recordIDs) == 0 {
|
||||
err = p.createRecord(ctx, client, recordType, ipStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
@@ -278,7 +278,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
for _, recordID := range recordIDs {
|
||||
err = p.updateRecord(ctx, client, recordType, ipStr, recordID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -98,7 +98,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
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),
|
||||
@@ -114,13 +114,13 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
@@ -130,38 +130,38 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
case http.StatusNoContent: // no change
|
||||
return ip, nil
|
||||
case http.StatusUnauthorized:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case http.StatusConflict:
|
||||
return nil, fmt.Errorf("%w", errors.ErrZoneNotFound)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrZoneNotFound)
|
||||
case http.StatusGone:
|
||||
return nil, fmt.Errorf("%w", errors.ErrAccountInactive)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAccountInactive)
|
||||
case http.StatusLengthRequired:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrMalformedIPSent, ip)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrMalformedIPSent, ip)
|
||||
case http.StatusPreconditionFailed:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrPrivateIPSent, ip)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrPrivateIPSent, ip)
|
||||
case http.StatusServiceUnavailable:
|
||||
return nil, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrDNSServerSide)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.HasPrefix(s, "abuse"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case strings.HasPrefix(s, "badrequest"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(s, "good"), strings.HasPrefix(s, "nochg"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -104,9 +104,9 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
recordType := constants.A
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordType = constants.AAAA
|
||||
}
|
||||
u := url.URL{
|
||||
@@ -136,12 +136,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
encoder := json.NewEncoder(buffer)
|
||||
err = encoder.Encode(requestData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrRequestEncode, err)
|
||||
}
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetContentType(request, "application/json")
|
||||
headers.SetXAuthUsername(request, p.username)
|
||||
@@ -150,12 +150,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode > http.StatusUnsupportedMediaType {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.BodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
@@ -168,11 +168,11 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
err = decoder.Decode(&parsedJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
|
||||
if parsedJSON.Message != "ok" {
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, parsedJSON.Error)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnsuccessfulResponse, parsedJSON.Error)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -104,7 +104,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
// see https://wiki.securepoint.de/SPDyn/Variablen
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -130,42 +130,42 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
bodyString := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(bodyString))
|
||||
}
|
||||
|
||||
switch {
|
||||
case isAny(bodyString, constants.Abuse, "numhost"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case isAny(bodyString, constants.Badauth, "!yours"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.HasPrefix(bodyString, "good"):
|
||||
return ip, nil
|
||||
case bodyString == constants.Notfqdn:
|
||||
return nil, fmt.Errorf("%w: not fqdn", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w: not fqdn", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(bodyString, "nochg"):
|
||||
return ip, nil
|
||||
case isAny(bodyString, "nohost", "fatal"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, bodyString)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, bodyString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -93,7 +93,7 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
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.domain, p.password),
|
||||
@@ -109,38 +109,38 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
str := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, str)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrBadHTTPStatus, response.StatusCode, str)
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(str, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.HasPrefix(str, constants.Abuse):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAbuse)
|
||||
case strings.HasPrefix(str, "badrequest"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(str, "constants.Badauth"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrAuth)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.HasPrefix(str, "good"), strings.HasPrefix(str, "nochg"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, str)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, str)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -98,10 +98,10 @@ func (p *Provider) HTML() models.HTMLRow {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
host := "dyndns.variomedia.de"
|
||||
if p.useProviderIP {
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
host = "dyndns6.variomedia.de"
|
||||
} else {
|
||||
host = "dyndns4.variomedia.de"
|
||||
@@ -123,35 +123,35 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip net.IP) (
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
headers.SetUserAgent(request)
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", errors.ErrUnmarshalResponse, err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %d: %s",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrBadHTTPStatus, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(s, constants.Notfqdn):
|
||||
return nil, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.HasPrefix(s, "badrequest"):
|
||||
return nil, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBadRequest)
|
||||
case strings.HasPrefix(s, "good"):
|
||||
return ip, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
"github.com/qdm12/ddns-updater/internal/settings/constants"
|
||||
@@ -56,7 +56,7 @@ type Settings interface {
|
||||
HTML() models.HTMLRow
|
||||
Proxied() bool
|
||||
IPVersion() ipversion.IPVersion
|
||||
Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error)
|
||||
Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error)
|
||||
}
|
||||
|
||||
var ErrProviderUnknown = errors.New("unknown provider")
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
@@ -16,29 +15,26 @@ func MatchEmail(email string) bool {
|
||||
return regexEmail.MatchString(email)
|
||||
}
|
||||
|
||||
func FindIPv4Addresses(text string) (addresses []net.IP) {
|
||||
func FindIPv4Addresses(text string) (addresses []netip.Addr) {
|
||||
const n = -1
|
||||
ipv4Strings := regexIPv4.FindAllString(text, n)
|
||||
return mustParseIPAddresses(ipv4Strings)
|
||||
}
|
||||
|
||||
func FindIPv6Addresses(text string) (addresses []net.IP) {
|
||||
func FindIPv6Addresses(text string) (addresses []netip.Addr) {
|
||||
const n = -1
|
||||
ipv6Strings := regexIPv6.FindAllString(text, n)
|
||||
return mustParseIPAddresses(ipv6Strings)
|
||||
}
|
||||
|
||||
func mustParseIPAddresses(ipStrings []string) (addresses []net.IP) {
|
||||
func mustParseIPAddresses(ipStrings []string) (addresses []netip.Addr) {
|
||||
if len(ipStrings) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
addresses = make([]net.IP, len(ipStrings))
|
||||
addresses = make([]netip.Addr, len(ipStrings))
|
||||
for i, ipString := range ipStrings {
|
||||
addresses[i] = net.ParseIP(ipString)
|
||||
if addresses[i] == nil {
|
||||
panic(fmt.Sprintf("invalid IP address: %q", ipString))
|
||||
}
|
||||
addresses[i] = netip.MustParseAddr(ipString)
|
||||
}
|
||||
|
||||
return addresses
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -12,7 +12,7 @@ func Test_FindIPv4Addresses(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
text string
|
||||
addresses []net.IP
|
||||
addresses []netip.Addr
|
||||
}{
|
||||
"empty_string": {},
|
||||
"no_address": {
|
||||
@@ -20,15 +20,15 @@ func Test_FindIPv4Addresses(t *testing.T) {
|
||||
},
|
||||
"single_address_exact": {
|
||||
text: "192.168.1.5",
|
||||
addresses: []net.IP{net.IPv4(192, 168, 1, 5)},
|
||||
addresses: []netip.Addr{netip.AddrFrom4([4]byte{192, 168, 1, 5})},
|
||||
},
|
||||
"multiple_in_text": {
|
||||
text: "sd 192.168.1.5 1.5 1.3.5.4",
|
||||
addresses: []net.IP{net.IPv4(192, 168, 1, 5), net.IPv4(1, 3, 5, 4)},
|
||||
addresses: []netip.Addr{netip.AddrFrom4([4]byte{192, 168, 1, 5}), netip.AddrFrom4([4]byte{1, 3, 5, 4})},
|
||||
},
|
||||
"longer_than_normal_ip": {
|
||||
text: "0.0.0.0.0",
|
||||
addresses: []net.IP{net.IPv4(0, 0, 0, 0)},
|
||||
addresses: []netip.Addr{netip.AddrFrom4([4]byte{0, 0, 0, 0})},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
@@ -47,7 +47,7 @@ func Test_FindIPv6Addresses(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
text string
|
||||
addresses []net.IP
|
||||
addresses []netip.Addr
|
||||
}{
|
||||
"empty_string": {},
|
||||
"no_address": {
|
||||
@@ -58,13 +58,13 @@ func Test_FindIPv6Addresses(t *testing.T) {
|
||||
},
|
||||
"single_address_exact": {
|
||||
text: "::1",
|
||||
addresses: []net.IP{net.ParseIP("::1")},
|
||||
addresses: []netip.Addr{netip.MustParseAddr("::1")},
|
||||
},
|
||||
"multiple_in_text": {
|
||||
text: "2001:0db8:85a3:0000:0000:8a2e:0370:7334 sdas ::1",
|
||||
addresses: []net.IP{
|
||||
net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
net.ParseIP("::1"),
|
||||
addresses: []netip.Addr{
|
||||
netip.MustParseAddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
netip.MustParseAddr("::1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
||||
)
|
||||
|
||||
type getIPFunc func(ctx context.Context) (ip net.IP, err error)
|
||||
type getIPFunc func(ctx context.Context) (ip netip.Addr, err error)
|
||||
|
||||
func tryAndRepeatGettingIP(ctx context.Context, getIPFunc getIPFunc,
|
||||
logger Logger, version ipversion.IPVersion) (ip net.IP, err error) {
|
||||
logger Logger, version ipversion.IPVersion) (ip netip.Addr, err error) {
|
||||
const tries = 3
|
||||
logMessagePrefix := "obtaining " + version.String() + " address"
|
||||
for try := 0; try < tries; try++ {
|
||||
|
||||
@@ -3,19 +3,20 @@ package update
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/records"
|
||||
)
|
||||
|
||||
type PublicIPFetcher interface {
|
||||
IP(ctx context.Context) (net.IP, error)
|
||||
IP4(ctx context.Context) (net.IP, error)
|
||||
IP6(ctx context.Context) (net.IP, error)
|
||||
IP(ctx context.Context) (netip.Addr, error)
|
||||
IP4(ctx context.Context) (netip.Addr, error)
|
||||
IP6(ctx context.Context) (netip.Addr, error)
|
||||
}
|
||||
|
||||
type UpdaterInterface interface {
|
||||
Update(ctx context.Context, recordID uint, ip net.IP, now time.Time) (err error)
|
||||
Update(ctx context.Context, recordID uint, ip netip.Addr, now time.Time) (err error)
|
||||
}
|
||||
|
||||
type Database interface {
|
||||
|
||||
15
internal/update/mask.go
Normal file
15
internal/update/mask.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func mustMaskIPv6(ipv6 netip.Addr, ipv6MaskBits uint8) (maskedIPv6 netip.Addr) {
|
||||
prefix, err := ipv6.Prefix(int(ipv6MaskBits))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("getting masked IPv6 prefix: %s", err))
|
||||
}
|
||||
maskedIPv6 = prefix.Addr()
|
||||
return maskedIPv6
|
||||
}
|
||||
19
internal/update/mask_test.go
Normal file
19
internal/update/mask_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_mustMaskIPv6(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const maskBits = 24
|
||||
ip := netip.AddrFrom4([4]byte{1, 2, 3, 4})
|
||||
maskedIP := mustMaskIPv6(ip, maskBits)
|
||||
|
||||
expected := netip.AddrFrom4([4]byte{1, 2, 3, 0})
|
||||
assert.Equal(t, expected, maskedIP)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package update
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/constants"
|
||||
@@ -13,55 +13,67 @@ import (
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
period time.Duration
|
||||
db Database
|
||||
updater UpdaterInterface
|
||||
force chan struct{}
|
||||
forceResult chan []error
|
||||
ipv6Mask net.IPMask
|
||||
cooldown time.Duration
|
||||
resolver LookupIPer
|
||||
ipGetter PublicIPFetcher
|
||||
logger Logger
|
||||
timeNow func() time.Time
|
||||
period time.Duration
|
||||
db Database
|
||||
updater UpdaterInterface
|
||||
force chan struct{}
|
||||
forceResult chan []error
|
||||
ipv6MaskBits uint8
|
||||
cooldown time.Duration
|
||||
resolver LookupIPer
|
||||
ipGetter PublicIPFetcher
|
||||
logger Logger
|
||||
timeNow func() time.Time
|
||||
}
|
||||
|
||||
func NewRunner(db Database, updater UpdaterInterface, ipGetter PublicIPFetcher,
|
||||
period time.Duration, ipv6Mask net.IPMask, cooldown time.Duration,
|
||||
period time.Duration, ipv6MaskBits uint8, cooldown time.Duration,
|
||||
logger Logger, resolver LookupIPer, timeNow func() time.Time) *Runner {
|
||||
return &Runner{
|
||||
period: period,
|
||||
db: db,
|
||||
updater: updater,
|
||||
force: make(chan struct{}),
|
||||
forceResult: make(chan []error),
|
||||
ipv6Mask: ipv6Mask,
|
||||
cooldown: cooldown,
|
||||
resolver: resolver,
|
||||
ipGetter: ipGetter,
|
||||
logger: logger,
|
||||
timeNow: timeNow,
|
||||
period: period,
|
||||
db: db,
|
||||
updater: updater,
|
||||
force: make(chan struct{}),
|
||||
forceResult: make(chan []error),
|
||||
ipv6MaskBits: ipv6MaskBits,
|
||||
cooldown: cooldown,
|
||||
resolver: resolver,
|
||||
ipGetter: ipGetter,
|
||||
logger: logger,
|
||||
timeNow: timeNow,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) lookupIPsResilient(ctx context.Context, hostname string, tries int) (
|
||||
ipv4 net.IP, ipv6 net.IP, err error) {
|
||||
ipv4 netip.Addr, ipv6 netip.Addr, err error) {
|
||||
for i := 0; i < tries; i++ {
|
||||
ipv4, ipv6, err = r.lookupIPs(ctx, hostname)
|
||||
if err == nil {
|
||||
return ipv4, ipv6, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, err
|
||||
return netip.Addr{}, netip.Addr{}, err
|
||||
}
|
||||
|
||||
func (r *Runner) lookupIPs(ctx context.Context, hostname string) (ipv4 net.IP, ipv6 net.IP, err error) {
|
||||
ips, err := r.resolver.LookupIP(ctx, "ip", hostname)
|
||||
func (r *Runner) lookupIPs(ctx context.Context, hostname string) (
|
||||
ipv4 netip.Addr, ipv6 netip.Addr, err error) {
|
||||
netIPs, err := r.resolver.LookupIP(ctx, "ip", hostname)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return netip.Addr{}, netip.Addr{}, err
|
||||
}
|
||||
ips := make([]netip.Addr, len(netIPs))
|
||||
for i, netIP := range netIPs {
|
||||
switch {
|
||||
case netIP == nil:
|
||||
case netIP.To4() != nil:
|
||||
ips[i] = netip.AddrFrom4([4]byte(netIP.To4()))
|
||||
default: // IPv6
|
||||
ips[i] = netip.AddrFrom16([16]byte(netIP.To16()))
|
||||
}
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
ipv6 = ip
|
||||
} else {
|
||||
ipv4 = ip
|
||||
@@ -87,15 +99,15 @@ func doIPVersion(records []librecords.Record) (doIP, doIPv4, doIPv6 bool) {
|
||||
return doIP, doIPv4, doIPv6
|
||||
}
|
||||
|
||||
func (r *Runner) getNewIPs(ctx context.Context, doIP, doIPv4, doIPv6 bool, ipv6Mask net.IPMask) (
|
||||
ip, ipv4, ipv6 net.IP, errors []error) {
|
||||
func (r *Runner) getNewIPs(ctx context.Context, doIP, doIPv4, doIPv6 bool, ipv6MaskBits uint8) (
|
||||
ip, ipv4, ipv6 netip.Addr, errors []error) {
|
||||
var err error
|
||||
if doIP {
|
||||
ip, err = tryAndRepeatGettingIP(ctx, r.ipGetter.IP, r.logger, ipversion.IP4or6)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
} else if ip.To4() == nil {
|
||||
ip = ip.Mask(ipv6Mask)
|
||||
} else if ip.Is6() {
|
||||
ip = mustMaskIPv6(ip, ipv6MaskBits)
|
||||
}
|
||||
}
|
||||
if doIPv4 {
|
||||
@@ -109,17 +121,17 @@ func (r *Runner) getNewIPs(ctx context.Context, doIP, doIPv4, doIPv6 bool, ipv6M
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
} else {
|
||||
ipv6 = ipv6.Mask(ipv6Mask)
|
||||
ip = mustMaskIPv6(ip, ipv6MaskBits)
|
||||
}
|
||||
}
|
||||
return ip, ipv4, ipv6, errors
|
||||
}
|
||||
|
||||
func (r *Runner) getRecordIDsToUpdate(ctx context.Context, records []librecords.Record,
|
||||
ip, ipv4, ipv6 net.IP, now time.Time, ipv6Mask net.IPMask) (recordIDs map[uint]struct{}) {
|
||||
ip, ipv4, ipv6 netip.Addr, now time.Time, ipv6MaskBits uint8) (recordIDs map[uint]struct{}) {
|
||||
recordIDs = make(map[uint]struct{})
|
||||
for i, record := range records {
|
||||
if shouldUpdate := r.shouldUpdateRecord(ctx, record, ip, ipv4, ipv6, now, ipv6Mask); shouldUpdate {
|
||||
if shouldUpdate := r.shouldUpdateRecord(ctx, record, ip, ipv4, ipv6, now, ipv6MaskBits); shouldUpdate {
|
||||
id := uint(i)
|
||||
recordIDs[id] = struct{}{}
|
||||
}
|
||||
@@ -128,7 +140,7 @@ func (r *Runner) getRecordIDsToUpdate(ctx context.Context, records []librecords.
|
||||
}
|
||||
|
||||
func (r *Runner) shouldUpdateRecord(ctx context.Context, record librecords.Record,
|
||||
ip, ipv4, ipv6 net.IP, now time.Time, ipv6Mask net.IPMask) (update bool) {
|
||||
ip, ipv4, ipv6 netip.Addr, now time.Time, ipv6MaskBits uint8) (update bool) {
|
||||
isWithinBanPeriod := record.LastBan != nil && now.Sub(*record.LastBan) < time.Hour
|
||||
isWithinCooldown := now.Sub(record.History.GetSuccessTime()) < r.cooldown
|
||||
if isWithinBanPeriod || isWithinCooldown {
|
||||
@@ -143,14 +155,14 @@ func (r *Runner) shouldUpdateRecord(ctx context.Context, record librecords.Recor
|
||||
lastIP := record.History.GetCurrentIP() // can be nil
|
||||
return r.shouldUpdateRecordNoLookup(hostname, ipVersion, lastIP, ip, ipv4, ipv6)
|
||||
}
|
||||
return r.shouldUpdateRecordWithLookup(ctx, hostname, ipVersion, ip, ipv4, ipv6, ipv6Mask)
|
||||
return r.shouldUpdateRecordWithLookup(ctx, hostname, ipVersion, ip, ipv4, ipv6, ipv6MaskBits)
|
||||
}
|
||||
|
||||
func (r *Runner) shouldUpdateRecordNoLookup(hostname string, ipVersion ipversion.IPVersion,
|
||||
lastIP, ip, ipv4, ipv6 net.IP) (update bool) {
|
||||
lastIP, ip, ipv4, ipv6 netip.Addr) (update bool) {
|
||||
switch ipVersion {
|
||||
case ipversion.IP4or6:
|
||||
if ip != nil && !ip.Equal(lastIP) {
|
||||
if ip.IsValid() && ip.Compare(lastIP) != 0 {
|
||||
r.logger.Info("Last IP address stored for " + hostname +
|
||||
" is " + lastIP.String() + " and your IP address is " + ip.String())
|
||||
return true
|
||||
@@ -158,7 +170,7 @@ func (r *Runner) shouldUpdateRecordNoLookup(hostname string, ipVersion ipversion
|
||||
r.logger.Debug("Last IP address stored for " + hostname + " is " +
|
||||
lastIP.String() + " and your IP address is " + ip.String() + ", skipping update")
|
||||
case ipversion.IP4:
|
||||
if ipv4 != nil && !ipv4.Equal(lastIP) {
|
||||
if ipv4.IsValid() && ipv4.Compare(lastIP) != 0 {
|
||||
r.logger.Info("Last IPv4 address stored for " + hostname +
|
||||
" is " + lastIP.String() + " and your IPv4 address is " + ip.String())
|
||||
return true
|
||||
@@ -166,7 +178,7 @@ func (r *Runner) shouldUpdateRecordNoLookup(hostname string, ipVersion ipversion
|
||||
r.logger.Debug("Last IPv4 address stored for " + hostname + " is " +
|
||||
lastIP.String() + " and your IPv4 address is " + ip.String() + ", skipping update")
|
||||
case ipversion.IP6:
|
||||
if ipv6 != nil && !ipv6.Equal(lastIP) {
|
||||
if ipv6.IsValid() && ipv6.Compare(lastIP) != 0 {
|
||||
r.logger.Info("Last IPv6 address stored for " + hostname +
|
||||
" is " + lastIP.String() + " and your IPv6 address is " + ip.String())
|
||||
return true
|
||||
@@ -178,7 +190,7 @@ func (r *Runner) shouldUpdateRecordNoLookup(hostname string, ipVersion ipversion
|
||||
}
|
||||
|
||||
func (r *Runner) shouldUpdateRecordWithLookup(ctx context.Context, hostname string, ipVersion ipversion.IPVersion,
|
||||
ip, ipv4, ipv6 net.IP, ipv6Mask net.IPMask) (update bool) {
|
||||
ip, ipv4, ipv6 netip.Addr, ipv6MaskBits uint8) (update bool) {
|
||||
const tries = 5
|
||||
recordIPv4, recordIPv6, err := r.lookupIPsResilient(ctx, hostname, tries)
|
||||
if err != nil {
|
||||
@@ -191,17 +203,17 @@ func (r *Runner) shouldUpdateRecordWithLookup(ctx context.Context, hostname stri
|
||||
fmt.Sprint(tries) + " tries: " + err.Error()) // update anyway
|
||||
}
|
||||
|
||||
if recordIPv6 != nil {
|
||||
recordIPv6 = recordIPv6.Mask(ipv6Mask)
|
||||
if recordIPv6.IsValid() {
|
||||
recordIPv6 = mustMaskIPv6(recordIPv6, ipv6MaskBits)
|
||||
}
|
||||
|
||||
switch ipVersion {
|
||||
case ipversion.IP4or6:
|
||||
recordIP := recordIPv4
|
||||
if ip.To4() == nil {
|
||||
if ip.Is6() {
|
||||
recordIP = recordIPv6
|
||||
}
|
||||
if ip != nil && !ip.Equal(recordIPv4) && !ip.Equal(recordIPv6) {
|
||||
if ip.IsValid() && ip.Compare(recordIPv4) != 0 && ip.Compare(recordIPv6) != 0 {
|
||||
r.logger.Info("IP address of " + hostname + " is " + recordIP.String() +
|
||||
" and your IP address is " + ip.String())
|
||||
return true
|
||||
@@ -209,7 +221,7 @@ func (r *Runner) shouldUpdateRecordWithLookup(ctx context.Context, hostname stri
|
||||
r.logger.Debug("IP address of " + hostname + " is " + recordIP.String() +
|
||||
" and your IP address is " + ip.String() + ", skipping update")
|
||||
case ipversion.IP4:
|
||||
if ipv4 != nil && !ipv4.Equal(recordIPv4) {
|
||||
if ipv4.IsValid() && ipv4.Compare(recordIPv4) != 0 {
|
||||
r.logger.Info("IPv4 address of " + hostname + " is " + recordIPv4.String() +
|
||||
" and your IPv4 address is " + ipv4.String())
|
||||
return true
|
||||
@@ -217,7 +229,7 @@ func (r *Runner) shouldUpdateRecordWithLookup(ctx context.Context, hostname stri
|
||||
r.logger.Debug("IPv4 address of " + hostname + " is " + recordIPv4.String() +
|
||||
" and your IPv4 address is " + ipv4.String() + ", skipping update")
|
||||
case ipversion.IP6:
|
||||
if ipv6 != nil && !ipv6.Equal(recordIPv6) {
|
||||
if ipv6.IsValid() && ipv6.Compare(recordIPv6) != 0 {
|
||||
r.logger.Info("IPv6 address of " + hostname + " is " + recordIPv6.String() +
|
||||
" and your IPv6 address is " + ipv6.String())
|
||||
return true
|
||||
@@ -228,7 +240,7 @@ func (r *Runner) shouldUpdateRecordWithLookup(ctx context.Context, hostname stri
|
||||
return false
|
||||
}
|
||||
|
||||
func getIPMatchingVersion(ip, ipv4, ipv6 net.IP, ipVersion ipversion.IPVersion) net.IP {
|
||||
func getIPMatchingVersion(ip, ipv4, ipv6 netip.Addr, ipVersion ipversion.IPVersion) netip.Addr {
|
||||
switch ipVersion {
|
||||
case ipversion.IP4or6:
|
||||
return ip
|
||||
@@ -237,17 +249,17 @@ func getIPMatchingVersion(ip, ipv4, ipv6 net.IP, ipVersion ipversion.IPVersion)
|
||||
case ipversion.IP6:
|
||||
return ipv6
|
||||
}
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
func setInitialUpToDateStatus(db Database, id uint, updateIP net.IP, now time.Time) error {
|
||||
func setInitialUpToDateStatus(db Database, id uint, updateIP netip.Addr, now time.Time) error {
|
||||
record, err := db.Select(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record.Status = constants.UPTODATE
|
||||
record.Time = now
|
||||
if record.History.GetCurrentIP() == nil {
|
||||
if !record.History.GetCurrentIP().IsValid() {
|
||||
record.History = append(record.History, models.HistoryEvent{
|
||||
IP: updateIP,
|
||||
Time: now,
|
||||
@@ -256,18 +268,18 @@ func setInitialUpToDateStatus(db Database, id uint, updateIP net.IP, now time.Ti
|
||||
return db.Update(id, record)
|
||||
}
|
||||
|
||||
func (r *Runner) updateNecessary(ctx context.Context, ipv6Mask net.IPMask) (errors []error) {
|
||||
func (r *Runner) updateNecessary(ctx context.Context, ipv6MaskBits uint8) (errors []error) {
|
||||
records := r.db.SelectAll()
|
||||
doIP, doIPv4, doIPv6 := doIPVersion(records)
|
||||
r.logger.Debug(fmt.Sprintf("configured to fetch IP: v4 or v6: %t, v4: %t, v6: %t", doIP, doIPv4, doIPv6))
|
||||
ip, ipv4, ipv6, errors := r.getNewIPs(ctx, doIP, doIPv4, doIPv6, ipv6Mask)
|
||||
ip, ipv4, ipv6, errors := r.getNewIPs(ctx, doIP, doIPv4, doIPv6, ipv6MaskBits)
|
||||
r.logger.Debug(fmt.Sprintf("your public IP address are: v4 or v6: %s, v4: %s, v6: %s", ip, ipv4, ipv6))
|
||||
for _, err := range errors {
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
now := r.timeNow()
|
||||
recordIDs := r.getRecordIDsToUpdate(ctx, records, ip, ipv4, ipv6, now, ipv6Mask)
|
||||
recordIDs := r.getRecordIDsToUpdate(ctx, records, ip, ipv4, ipv6, now, ipv6MaskBits)
|
||||
|
||||
for i, record := range records {
|
||||
id := uint(i)
|
||||
@@ -302,9 +314,9 @@ func (r *Runner) Run(ctx context.Context, done chan<- struct{}) {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
r.updateNecessary(ctx, r.ipv6Mask)
|
||||
r.updateNecessary(ctx, r.ipv6MaskBits)
|
||||
case <-r.force:
|
||||
r.forceResult <- r.updateNecessary(ctx, r.ipv6Mask)
|
||||
r.forceResult <- r.updateNecessary(ctx, r.ipv6MaskBits)
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/constants"
|
||||
@@ -32,7 +32,7 @@ func NewUpdater(db Database, client *http.Client, notify notifyFunc, logger Debu
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) Update(ctx context.Context, id uint, ip net.IP, now time.Time) (err error) {
|
||||
func (u *Updater) Update(ctx context.Context, id uint, ip netip.Addr, now time.Time) (err error) {
|
||||
record, err := u.db.Select(id)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
)
|
||||
|
||||
func fetch(ctx context.Context, client Client, providerData providerData) (
|
||||
publicIP net.IP, err error) {
|
||||
publicIP netip.Addr, err error) {
|
||||
message := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Opcode: dns.OpcodeQuery,
|
||||
@@ -34,34 +34,34 @@ func fetch(ctx context.Context, client Client, providerData providerData) (
|
||||
|
||||
r, _, err := client.ExchangeContext(ctx, message, providerData.nameserver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
L := len(r.Answer)
|
||||
if L == 0 {
|
||||
return nil, ErrNoTXTRecordFound
|
||||
return netip.Addr{}, fmt.Errorf("%w", ErrNoTXTRecordFound)
|
||||
} else if L > 1 {
|
||||
return nil, fmt.Errorf("%w: %d instead of 1", ErrTooManyAnswers, L)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d instead of 1", ErrTooManyAnswers, L)
|
||||
}
|
||||
|
||||
answer := r.Answer[0]
|
||||
txt, ok := answer.(*dns.TXT)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %T instead of *dns.TXT",
|
||||
return netip.Addr{}, fmt.Errorf("%w: %T instead of *dns.TXT",
|
||||
ErrInvalidAnswerType, answer)
|
||||
}
|
||||
|
||||
L = len(txt.Txt)
|
||||
if L == 0 {
|
||||
return nil, ErrNoTXTRecordFound
|
||||
return netip.Addr{}, fmt.Errorf("%w", ErrNoTXTRecordFound)
|
||||
} else if L > 1 {
|
||||
return nil, fmt.Errorf("%w: %d instead of 1", ErrTooManyTXTRecords, L)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d instead of 1", ErrTooManyTXTRecords, L)
|
||||
}
|
||||
ipString := txt.Txt[0]
|
||||
|
||||
publicIP = net.ParseIP(ipString)
|
||||
if publicIP == nil {
|
||||
return nil, fmt.Errorf("%w: %q", ErrIPMalformed, ipString)
|
||||
publicIP, err = netip.ParseAddr(ipString)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", ErrIPMalformed, err)
|
||||
}
|
||||
|
||||
return publicIP, nil
|
||||
|
||||
@@ -3,7 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -39,7 +39,7 @@ func Test_fetch(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
response *dns.Msg
|
||||
exchangeErr error
|
||||
publicIP net.IP
|
||||
publicIP netip.Addr
|
||||
err error
|
||||
}{
|
||||
"success": {
|
||||
@@ -50,7 +50,7 @@ func Test_fetch(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
publicIP: net.IP{55, 55, 55, 55},
|
||||
publicIP: netip.AddrFrom4([4]byte{55, 55, 55, 55}),
|
||||
},
|
||||
"exchange error": {
|
||||
exchangeErr: errors.New("dummy"),
|
||||
@@ -92,7 +92,7 @@ func Test_fetch(t *testing.T) {
|
||||
Txt: []string{"invalid"},
|
||||
}},
|
||||
},
|
||||
err: errors.New(`IP address malformed: "invalid"`),
|
||||
err: errors.New(`IP address malformed: ParseAddr("invalid"): unable to parse IP`),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ func Test_fetch(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if !testCase.publicIP.Equal(publicIP) {
|
||||
if testCase.publicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", testCase.publicIP, publicIP)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,24 +2,24 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (f *Fetcher) IP(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.client)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP4(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP4(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.client4)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP6(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP6(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.client6)
|
||||
}
|
||||
|
||||
func (f *Fetcher) ip(ctx context.Context, client Client) (
|
||||
publicIP net.IP, err error) {
|
||||
publicIP netip.Addr, err error) {
|
||||
index := int(atomic.AddUint32(f.ring.counter, 1)) % len(f.ring.providers)
|
||||
provider := f.ring.providers[index]
|
||||
return fetch(ctx, client, provider.data())
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -26,33 +26,33 @@ var (
|
||||
)
|
||||
|
||||
func fetch(ctx context.Context, client *http.Client, url string, version ipversion.IPVersion) (
|
||||
publicIP net.IP, err error) {
|
||||
publicIP netip.Addr, err error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
switch response.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusForbidden, http.StatusTooManyRequests:
|
||||
return nil, fmt.Errorf("%w: %d (%s)", ErrBanned,
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d (%s)", ErrBanned,
|
||||
response.StatusCode, bodyToSingleLine(response.Body))
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
err = response.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
|
||||
s := string(b)
|
||||
@@ -69,39 +69,39 @@ func fetch(ctx context.Context, client *http.Client, url string, version ipversi
|
||||
case len(ipv6Strings) == 1:
|
||||
ipString = ipv6Strings[0]
|
||||
case len(ipv4Strings) > 1:
|
||||
return nil, fmt.Errorf("%w: found %d IPv4 addresses instead of 1",
|
||||
return netip.Addr{}, fmt.Errorf("%w: found %d IPv4 addresses instead of 1",
|
||||
ErrTooManyIPs, len(ipv4Strings))
|
||||
case len(ipv6Strings) > 1:
|
||||
return nil, fmt.Errorf("%w: found %d IPv6 addresses instead of 1",
|
||||
return netip.Addr{}, fmt.Errorf("%w: found %d IPv6 addresses instead of 1",
|
||||
ErrTooManyIPs, len(ipv6Strings))
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: from %q", ErrNoIPFound, url)
|
||||
return netip.Addr{}, fmt.Errorf("%w: from %q", ErrNoIPFound, url)
|
||||
}
|
||||
case ipversion.IP4:
|
||||
switch len(ipv4Strings) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("%w: from %q for version %s", ErrNoIPFound, url, version)
|
||||
return netip.Addr{}, fmt.Errorf("%w: from %q for version %s", ErrNoIPFound, url, version)
|
||||
case 1:
|
||||
ipString = ipv4Strings[0]
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: found %d IPv4 addresses instead of 1",
|
||||
return netip.Addr{}, fmt.Errorf("%w: found %d IPv4 addresses instead of 1",
|
||||
ErrTooManyIPs, len(ipv4Strings))
|
||||
}
|
||||
case ipversion.IP6:
|
||||
switch len(ipv6Strings) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("%w: from %q for version %s", ErrNoIPFound, url, version)
|
||||
return netip.Addr{}, fmt.Errorf("%w: from %q for version %s", ErrNoIPFound, url, version)
|
||||
case 1:
|
||||
ipString = ipv6Strings[0]
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: found %d IPv6 addresses instead of 1",
|
||||
return netip.Addr{}, fmt.Errorf("%w: found %d IPv6 addresses instead of 1",
|
||||
ErrTooManyIPs, len(ipv6Strings))
|
||||
}
|
||||
}
|
||||
|
||||
publicIP = net.ParseIP(ipString)
|
||||
if publicIP == nil {
|
||||
return nil, fmt.Errorf("%w: %s", ErrIPMalformed, ipString)
|
||||
publicIP, err = netip.ParseAddr(ipString)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %w", ErrIPMalformed, err)
|
||||
}
|
||||
|
||||
return publicIP, nil
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
||||
@@ -28,7 +28,7 @@ func Test_fetch(t *testing.T) {
|
||||
version ipversion.IPVersion
|
||||
httpContent []byte
|
||||
httpErr error
|
||||
publicIP net.IP
|
||||
publicIP netip.Addr
|
||||
err error
|
||||
}{
|
||||
"canceled context": {
|
||||
@@ -62,24 +62,24 @@ func Test_fetch(t *testing.T) {
|
||||
url: "https://opendns.com/ip",
|
||||
version: ipversion.IP4or6,
|
||||
httpContent: []byte(`1.67.201.251`),
|
||||
publicIP: net.IP{1, 67, 201, 251},
|
||||
publicIP: netip.AddrFrom4([4]byte{1, 67, 201, 251}),
|
||||
},
|
||||
"single IPv6 for IP4or6": {
|
||||
ctx: context.Background(),
|
||||
url: "https://opendns.com/ip",
|
||||
version: ipversion.IP4or6,
|
||||
httpContent: []byte(`::1`),
|
||||
publicIP: net.IP{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||
},
|
||||
publicIP: netip.AddrFrom16([16]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
}),
|
||||
},
|
||||
"IPv4 and IPv6 for IP4or6": {
|
||||
ctx: context.Background(),
|
||||
url: "https://opendns.com/ip",
|
||||
version: ipversion.IP4or6,
|
||||
httpContent: []byte(`1.67.201.251 ::1`),
|
||||
publicIP: net.IP{1, 67, 201, 251},
|
||||
publicIP: netip.AddrFrom4([4]byte{1, 67, 201, 251}),
|
||||
},
|
||||
"too many IPv4s for IP4or6": {
|
||||
ctx: context.Background(),
|
||||
@@ -107,7 +107,7 @@ func Test_fetch(t *testing.T) {
|
||||
url: "https://opendns.com/ip",
|
||||
version: ipversion.IP4,
|
||||
httpContent: []byte(`1.67.201.251`),
|
||||
publicIP: net.IP{1, 67, 201, 251},
|
||||
publicIP: netip.AddrFrom4([4]byte{1, 67, 201, 251}),
|
||||
},
|
||||
"too many IPv4s for IP4": {
|
||||
ctx: context.Background(),
|
||||
@@ -128,10 +128,10 @@ func Test_fetch(t *testing.T) {
|
||||
url: "https://opendns.com/ip",
|
||||
version: ipversion.IP6,
|
||||
httpContent: []byte(`::1`),
|
||||
publicIP: net.IP{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||
},
|
||||
publicIP: netip.AddrFrom16([16]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
}),
|
||||
},
|
||||
"too many IPv6s for IP6": {
|
||||
ctx: context.Background(),
|
||||
@@ -171,7 +171,7 @@ func Test_fetch(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if !tc.publicIP.Equal(publicIP) {
|
||||
if tc.publicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", tc.publicIP, publicIP)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,26 +4,26 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
||||
)
|
||||
|
||||
func (f *Fetcher) IP(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.ip4or6, ipversion.IP4or6)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP4(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP4(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.ip4, ipversion.IP4)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP6(ctx context.Context) (publicIP net.IP, err error) {
|
||||
func (f *Fetcher) IP6(ctx context.Context) (publicIP netip.Addr, err error) {
|
||||
return f.ip(ctx, f.ip6, ipversion.IP6)
|
||||
}
|
||||
|
||||
func (f *Fetcher) ip(ctx context.Context, ring *urlsRing, version ipversion.IPVersion) (
|
||||
publicIP net.IP, err error) {
|
||||
publicIP netip.Addr, err error) {
|
||||
ring.mutex.Lock()
|
||||
|
||||
var index int
|
||||
@@ -39,7 +39,7 @@ func (f *Fetcher) ip(ctx context.Context, ring *urlsRing, version ipversion.IPVe
|
||||
if banned == len(ring.urls) {
|
||||
banString := ring.banString()
|
||||
ring.mutex.Unlock()
|
||||
return nil, fmt.Errorf("%w: %s", ErrBanned, banString)
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", ErrBanned, banString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (f *Fetcher) ip(ctx context.Context, ring *urlsRing, version ipversion.IPVe
|
||||
ring.banned[index] = strings.ReplaceAll(err.Error(), ErrBanned.Error()+": ", "")
|
||||
ring.mutex.Unlock()
|
||||
}
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
return publicIP, nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +19,7 @@ func Test_fetcher_IP(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const url = "c"
|
||||
httpBytes := []byte(`55.55.55.55`)
|
||||
expectedPublicIP := net.IP{55, 55, 55, 55}
|
||||
expectedPublicIP := netip.AddrFrom4([4]byte{55, 55, 55, 55})
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
@@ -51,7 +51,7 @@ func Test_fetcher_IP(t *testing.T) {
|
||||
publicIP, err := initialFetcher.IP(ctx)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if !expectedPublicIP.Equal(publicIP) {
|
||||
if expectedPublicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", expectedPublicIP, publicIP)
|
||||
}
|
||||
assert.Equal(t, expectedFetcher, initialFetcher)
|
||||
@@ -63,7 +63,7 @@ func Test_fetcher_IP4(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const url = "c"
|
||||
httpBytes := []byte(`55.55.55.55`)
|
||||
expectedPublicIP := net.IP{55, 55, 55, 55}
|
||||
expectedPublicIP := netip.AddrFrom4([4]byte{55, 55, 55, 55})
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
@@ -95,7 +95,7 @@ func Test_fetcher_IP4(t *testing.T) {
|
||||
publicIP, err := initialFetcher.IP4(ctx)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if !expectedPublicIP.Equal(publicIP) {
|
||||
if expectedPublicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", expectedPublicIP, publicIP)
|
||||
}
|
||||
assert.Equal(t, expectedFetcher, initialFetcher)
|
||||
@@ -107,10 +107,9 @@ func Test_fetcher_IP6(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const url = "c"
|
||||
httpBytes := []byte(`::1`)
|
||||
expectedPublicIP := net.IP{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||
}
|
||||
expectedPublicIP := netip.AddrFrom16([16]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1})
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
@@ -142,7 +141,7 @@ func Test_fetcher_IP6(t *testing.T) {
|
||||
publicIP, err := initialFetcher.IP6(ctx)
|
||||
|
||||
assert.NoError(t, err)
|
||||
if !expectedPublicIP.Equal(publicIP) {
|
||||
if expectedPublicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", expectedPublicIP, publicIP)
|
||||
}
|
||||
assert.Equal(t, expectedFetcher, initialFetcher)
|
||||
@@ -176,7 +175,7 @@ func Test_fetcher_ip(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
initialFetcher *Fetcher
|
||||
ctx context.Context
|
||||
publicIP net.IP
|
||||
publicIP netip.Addr
|
||||
err error
|
||||
errMessage string
|
||||
finalFetcher *Fetcher // client is ignored when comparing the two
|
||||
@@ -191,7 +190,7 @@ func Test_fetcher_ip(t *testing.T) {
|
||||
urls: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
publicIP: net.IP{55, 55, 55, 55},
|
||||
publicIP: netip.AddrFrom4([4]byte{55, 55, 55, 55}),
|
||||
finalFetcher: &Fetcher{
|
||||
timeout: time.Hour,
|
||||
ip4or6: &urlsRing{
|
||||
@@ -210,7 +209,7 @@ func Test_fetcher_ip(t *testing.T) {
|
||||
urls: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
publicIP: net.IP{55, 55, 55, 55},
|
||||
publicIP: netip.AddrFrom4([4]byte{55, 55, 55, 55}),
|
||||
finalFetcher: &Fetcher{
|
||||
timeout: time.Hour,
|
||||
ip4or6: &urlsRing{
|
||||
@@ -276,7 +275,7 @@ func Test_fetcher_ip(t *testing.T) {
|
||||
banned: map[int]string{1: "banned"},
|
||||
},
|
||||
},
|
||||
publicIP: net.IP{55, 55, 55, 55},
|
||||
publicIP: netip.AddrFrom4([4]byte{55, 55, 55, 55}),
|
||||
},
|
||||
"all banned": {
|
||||
ctx: context.Background(),
|
||||
@@ -335,7 +334,7 @@ func Test_fetcher_ip(t *testing.T) {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
|
||||
if !testCase.publicIP.Equal(publicIP) {
|
||||
if testCase.publicIP.Compare(publicIP) != 0 {
|
||||
t.Errorf("IP address mismatch: expected %s and got %s", testCase.publicIP, publicIP)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ func New(client *http.Client, options ...Option) (info *Info, err error) {
|
||||
// Get finds IP information for the given IP address using one of
|
||||
// the ip data provider picked at random. A `nil` IP address can be
|
||||
// given to signal to fetch information on the current public IP address.
|
||||
func (i *Info) Get(ctx context.Context, ip net.IP) (result Result, err error) {
|
||||
func (i *Info) Get(ctx context.Context, ip netip.Addr) (result Result, err error) {
|
||||
if len(i.providers) == 1 {
|
||||
return i.providers[0].get(ctx, ip)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (i *Info) Get(ctx context.Context, ip net.IP) (result Result, err error) {
|
||||
// GetMultiple finds IP information for the given IP addresses, each using
|
||||
// one of the ip data provider picked at random. It returns a slice of results
|
||||
// matching the order of the IP addresses given as argument.
|
||||
func (i *Info) GetMultiple(ctx context.Context, ips []net.IP) (results []Result, err error) {
|
||||
func (i *Info) GetMultiple(ctx context.Context, ips []netip.Addr) (results []Result, err error) {
|
||||
type resultWithError struct {
|
||||
index int
|
||||
result Result
|
||||
@@ -92,7 +92,7 @@ func (i *Info) GetMultiple(ctx context.Context, ips []net.IP) (results []Result,
|
||||
channel := make(chan resultWithError)
|
||||
|
||||
for index, ip := range ips {
|
||||
go func(ctx context.Context, index int, ip net.IP) {
|
||||
go func(ctx context.Context, index int, ip netip.Addr) {
|
||||
result := resultWithError{
|
||||
index: index,
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func newIpinfo(client *http.Client) *ipinfo {
|
||||
@@ -18,12 +18,12 @@ type ipinfo struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (p *ipinfo) get(ctx context.Context, ip net.IP) (
|
||||
func (p *ipinfo) get(ctx context.Context, ip netip.Addr) (
|
||||
result Result, err error) {
|
||||
result.Source = string(Ipinfo)
|
||||
|
||||
url := "https://ipinfo.io/"
|
||||
if ip != nil {
|
||||
if ip.IsValid() {
|
||||
url += ip.String()
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
@@ -51,10 +51,10 @@ func (p *ipinfo) get(ctx context.Context, ip net.IP) (
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var data struct {
|
||||
IP net.IP `json:"ip"`
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Provider string
|
||||
@@ -32,7 +32,7 @@ func ValidateProvider(provider Provider) error {
|
||||
}
|
||||
|
||||
type provider interface {
|
||||
get(ctx context.Context, ip net.IP) (result Result, err error)
|
||||
get(ctx context.Context, ip netip.Addr) (result Result, err error)
|
||||
}
|
||||
|
||||
func newProvider(providerName Provider, client *http.Client) provider { //nolint:ireturn
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package info
|
||||
|
||||
import "net"
|
||||
import "net/netip"
|
||||
|
||||
type Result struct {
|
||||
IP net.IP
|
||||
IP netip.Addr
|
||||
Country *string
|
||||
Region *string
|
||||
City *string
|
||||
|
||||
@@ -3,16 +3,16 @@ package publicip
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/dns"
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/http"
|
||||
)
|
||||
|
||||
type ipFetcher interface {
|
||||
IP(ctx context.Context) (ip net.IP, err error)
|
||||
IP4(ctx context.Context) (ipv4 net.IP, err error)
|
||||
IP6(ctx context.Context) (ipv6 net.IP, err error)
|
||||
IP(ctx context.Context) (ip netip.Addr, err error)
|
||||
IP4(ctx context.Context) (ipv4 netip.Addr, err error)
|
||||
IP6(ctx context.Context) (ipv6 netip.Addr, err error)
|
||||
}
|
||||
|
||||
type Fetcher struct {
|
||||
@@ -58,14 +58,14 @@ func NewFetcher(dnsSettings DNSSettings, httpSettings HTTPSettings) (f *Fetcher,
|
||||
return fetcher, nil
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP(ctx context.Context) (ip net.IP, err error) {
|
||||
func (f *Fetcher) IP(ctx context.Context) (ip netip.Addr, err error) {
|
||||
return f.getSubFetcher().IP(ctx)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP4(ctx context.Context) (ipv4 net.IP, err error) {
|
||||
func (f *Fetcher) IP4(ctx context.Context) (ipv4 netip.Addr, err error) {
|
||||
return f.getSubFetcher().IP4(ctx)
|
||||
}
|
||||
|
||||
func (f *Fetcher) IP6(ctx context.Context) (ipv6 net.IP, err error) {
|
||||
func (f *Fetcher) IP6(ctx context.Context) (ipv6 netip.Addr, err error) {
|
||||
return f.getSubFetcher().IP6(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user