You've already forked ddns-updater
Add v4.ident.me resolver
This commit is contained in:
@@ -15,7 +15,7 @@ Before run, you need configure this environment variables.
|
|||||||
- `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones)
|
- `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones)
|
||||||
- `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record
|
- `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record
|
||||||
- `CHECK_INTERVAL_SECONDS` - (optional) how often will the ip address of the records be checked (default: `300`)
|
- `CHECK_INTERVAL_SECONDS` - (optional) how often will the ip address of the records be checked (default: `300`)
|
||||||
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. For now only resolving via `https://ifconfig.me` is implemented. (default: `ifconfig.me`)
|
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
cf-dns-updater:
|
cf-dns-updater:
|
||||||
image: mkelcik/cloudflare-ddns-update:latest
|
image: mkelcik/cloudflare-ddns-update:latest
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
||||||
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
cf-dns-updater:
|
cf-dns-updater:
|
||||||
image: mkelcik/cloudflare-ddns-update:latest
|
image: mkelcik/cloudflare-ddns-update:latest
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
|
||||||
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
- CLOUDFLARE_API_KEY=your_cloudflare_api_key
|
||||||
|
|||||||
10
main.go
10
main.go
@@ -17,13 +17,15 @@ type PublicIpResolver interface {
|
|||||||
ResolvePublicIp(ctx context.Context) (net.IP, error)
|
ResolvePublicIp(ctx context.Context) (net.IP, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getResolver(resolverName string) PublicIpResolver {
|
func getResolver(resolverName string) (PublicIpResolver, string) {
|
||||||
switch resolverName {
|
switch resolverName {
|
||||||
// HERE add another resolver if needed
|
// HERE add another resolver if needed
|
||||||
|
case public_resolvers.V4IdentMeTag:
|
||||||
|
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
|
||||||
case public_resolvers.IfConfigMeTag:
|
case public_resolvers.IfConfigMeTag:
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
return public_resolvers.NewDefaultIfConfigMe()
|
return public_resolvers.NewDefaultIfConfigMe(), public_resolvers.IfConfigMeTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +50,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// public ip resolver
|
// public ip resolver
|
||||||
publicIpResolver := getResolver(config.PublicIpResolverTag)
|
publicIpResolver, resolverTag := getResolver(config.PublicIpResolverTag)
|
||||||
|
|
||||||
checkFunc := func() {
|
checkFunc := func() {
|
||||||
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Printf("Current public ip `%s`", currentPublicIP)
|
log.Printf("Current public ip `%s` (%s)", currentPublicIP, resolverTag)
|
||||||
|
|
||||||
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
|
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
44
public_resolvers/base_resolver.go
Normal file
44
public_resolvers/base_resolver.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package public_resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Doer interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseResolver struct {
|
||||||
|
client Doer
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := i.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return net.IP{}, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipText, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(string(ipText)), nil
|
||||||
|
}
|
||||||
97
public_resolvers/base_resolver_test.go
Normal file
97
public_resolvers/base_resolver_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package public_resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoundTripFunc .
|
||||||
|
type RoundTripFunc func(req *http.Request) *http.Response
|
||||||
|
|
||||||
|
// RoundTrip .
|
||||||
|
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
||||||
|
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: RoundTripFunc(fn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
||||||
|
|
||||||
|
testUrl := "http://my-test-url.url"
|
||||||
|
testIp := `192.168.0.100`
|
||||||
|
|
||||||
|
client := NewTestClient(func(req *http.Request) *http.Response {
|
||||||
|
|
||||||
|
if req.URL.String() != testUrl {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: 500,
|
||||||
|
// Send response to be tested
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString(`invalid url`)),
|
||||||
|
// Must be set to non-nil value or it panics
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
// Send response to be tested
|
||||||
|
Body: io.NopCloser(bytes.NewBufferString(testIp)),
|
||||||
|
// Must be set to non-nil value or it panics
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client Doer
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want net.IP
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "check parse ip4",
|
||||||
|
fields: fields{
|
||||||
|
client: client,
|
||||||
|
url: testUrl,
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
},
|
||||||
|
want: net.ParseIP(testIp),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
i := baseResolver{
|
||||||
|
client: tt.fields.client,
|
||||||
|
url: tt.fields.url,
|
||||||
|
}
|
||||||
|
got, err := i.ResolvePublicIp(tt.args.ctx)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ResolvePublicIp() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ResolvePublicIp() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,17 @@
|
|||||||
package public_resolvers
|
package public_resolvers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IfConfigMeTag = "ifconfig.me"
|
IfConfigMeTag = "ifconfig.me"
|
||||||
)
|
|
||||||
|
|
||||||
type Doer interface {
|
|
||||||
Do(*http.Request) (*http.Response, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ifConfigMeUrl = "https://ifconfig.me"
|
ifConfigMeUrl = "https://ifconfig.me"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IfConfigMe struct {
|
type IfConfigMe struct {
|
||||||
client Doer
|
baseResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultIfConfigMe() *IfConfigMe {
|
func NewDefaultIfConfigMe() *IfConfigMe {
|
||||||
@@ -31,32 +20,11 @@ func NewDefaultIfConfigMe() *IfConfigMe {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIfConfigMe(c Doer) *IfConfigMe {
|
func NewIfConfigMe(client Doer) *IfConfigMe {
|
||||||
return &IfConfigMe{client: c}
|
return &IfConfigMe{
|
||||||
}
|
baseResolver: baseResolver{
|
||||||
|
client: client,
|
||||||
func (i IfConfigMe) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
url: ifConfigMeUrl,
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ifConfigMeUrl, nil)
|
},
|
||||||
if err != nil {
|
}
|
||||||
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := i.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return net.IP{}, err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = resp.Body.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipText, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.ParseIP(string(ipText)), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
public_resolvers/v4identme.go
Normal file
30
public_resolvers/v4identme.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package public_resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
V4IdentMeTag = "v4.ident.me"
|
||||||
|
v4IdentMeUrl = "https://v4.ident.me/"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V4IdentMe struct {
|
||||||
|
baseResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV4IdentMeDefault() *V4IdentMe {
|
||||||
|
return NewV4IdentMe(&http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewV4IdentMe(client Doer) *V4IdentMe {
|
||||||
|
return &V4IdentMe{
|
||||||
|
baseResolver: baseResolver{
|
||||||
|
client: client,
|
||||||
|
url: v4IdentMeUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user