You've already forked ddns-updater
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d52cbe920 | ||
|
|
6f1b45cf8a | ||
|
|
f859e86a08 | ||
|
|
bbcc6eaa44 | ||
|
|
3222a6c54c |
@@ -1,3 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
## What is Cloudflare Dynamic DNS?
|
## What is Cloudflare Dynamic DNS?
|
||||||
DNS records are static, and it does not play well with dynamic IP addresses. Now, to solve that problem, you’ll need to set up dynamic DNS. Cloudflare provides an API that allows you to manage DNS records programmatically.
|
DNS records are static, and it does not play well with dynamic IP addresses. Now, to solve that problem, you’ll need to set up dynamic DNS. Cloudflare provides an API that allows you to manage DNS records programmatically.
|
||||||
|
|
||||||
@@ -15,7 +17,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. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`
|
- `PUBLIC_IP_RESOLVER` - (optional) public ip address resolver. (default: `ifconfig.me`) Available: `ifconfig.me`, `v4.ident.me`, `1.1.1.1`
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -20,6 +20,8 @@ type PublicIpResolver interface {
|
|||||||
func getResolver(resolverName string) (PublicIpResolver, string) {
|
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.CloudflareTraceTag:
|
||||||
|
return public_resolvers.NewDefaultCloudflareTrace(), public_resolvers.CloudflareTraceTag
|
||||||
case public_resolvers.V4IdentMeTag:
|
case public_resolvers.V4IdentMeTag:
|
||||||
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
|
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
|
||||||
case public_resolvers.IfConfigMeTag:
|
case public_resolvers.IfConfigMeTag:
|
||||||
@@ -57,7 +59,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Printf("Current public ip `%s` (%s)", currentPublicIP, resolverTag)
|
log.Printf("Current public ip `%s` (resolver: %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 {
|
||||||
|
|||||||
@@ -2,19 +2,30 @@ package public_resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var NoIPInResponseError = errors.New("no ip found in response")
|
||||||
|
|
||||||
type Doer interface {
|
type Doer interface {
|
||||||
Do(*http.Request) (*http.Response, error)
|
Do(*http.Request) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ipParserFunc func(reader io.Reader) (string, error)
|
||||||
|
|
||||||
|
func defaultIpParser(reader io.Reader) (string, error) {
|
||||||
|
out, err := io.ReadAll(reader)
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
type baseResolver struct {
|
type baseResolver struct {
|
||||||
client Doer
|
client Doer
|
||||||
url string
|
url string
|
||||||
|
ipParser ipParserFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
||||||
@@ -35,10 +46,10 @@ func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
|
|||||||
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipText, err := io.ReadAll(resp.Body)
|
ipText, err := i.ipParser(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
return net.IP{}, fmt.Errorf("error reading body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return net.ParseIP(string(ipText)), nil
|
return net.ParseIP(ipText), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
|
||||||
func NewTestClient(fn RoundTripFunc) *http.Client {
|
func NewTestClient(fn RoundTripFunc) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: RoundTripFunc(fn),
|
Transport: fn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
|||||||
type fields struct {
|
type fields struct {
|
||||||
client Doer
|
client Doer
|
||||||
url string
|
url string
|
||||||
|
fn ipParserFunc
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -70,6 +71,7 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
|||||||
fields: fields{
|
fields: fields{
|
||||||
client: client,
|
client: client,
|
||||||
url: testUrl,
|
url: testUrl,
|
||||||
|
fn: defaultIpParser,
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -83,6 +85,7 @@ func Test_baseResolver_ResolvePublicIp(t *testing.T) {
|
|||||||
i := baseResolver{
|
i := baseResolver{
|
||||||
client: tt.fields.client,
|
client: tt.fields.client,
|
||||||
url: tt.fields.url,
|
url: tt.fields.url,
|
||||||
|
ipParser: tt.fields.fn,
|
||||||
}
|
}
|
||||||
got, err := i.ResolvePublicIp(tt.args.ctx)
|
got, err := i.ResolvePublicIp(tt.args.ctx)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
|
|||||||
49
public_resolvers/cloudflare_trace.go
Normal file
49
public_resolvers/cloudflare_trace.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package public_resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CloudflareTraceTag = "1.1.1.1"
|
||||||
|
CloudflareTraceUrl = "https://1.1.1.1/cdn-cgi/trace"
|
||||||
|
|
||||||
|
ipPrefix = "ip="
|
||||||
|
)
|
||||||
|
|
||||||
|
type CloudflareTrace struct {
|
||||||
|
baseResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultCloudflareTrace() *CloudflareTrace {
|
||||||
|
return NewCloudflareTrace(&http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudflareTraceResponseParser(reader io.Reader) (string, error) {
|
||||||
|
data, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range strings.Split(string(data), "\n") {
|
||||||
|
if strings.Index(row, ipPrefix) == 0 {
|
||||||
|
return strings.TrimSpace(strings.ReplaceAll(row, ipPrefix, "")), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", NoIPInResponseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloudflareTrace(client Doer) *CloudflareTrace {
|
||||||
|
return &CloudflareTrace{
|
||||||
|
baseResolver: baseResolver{
|
||||||
|
client: client,
|
||||||
|
url: CloudflareTraceUrl,
|
||||||
|
ipParser: cloudflareTraceResponseParser,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
78
public_resolvers/cloudflare_trace_test.go
Normal file
78
public_resolvers/cloudflare_trace_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package public_resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_cloudflareTraceResponseParser(t *testing.T) {
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
args: args{
|
||||||
|
reader: bytes.NewBuffer([]byte(`fl=31f118
|
||||||
|
h=1.1.1.1
|
||||||
|
ip=94.113.142.206
|
||||||
|
ts=1683145336.383
|
||||||
|
visit_scheme=https
|
||||||
|
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
|
||||||
|
colo=PRG
|
||||||
|
sliver=none
|
||||||
|
http=http/2
|
||||||
|
loc=CZ
|
||||||
|
tls=TLSv1.3
|
||||||
|
sni=off
|
||||||
|
warp=off
|
||||||
|
gateway=off
|
||||||
|
rbi=off
|
||||||
|
kex=X25519`)),
|
||||||
|
},
|
||||||
|
want: "94.113.142.206",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no ip in response",
|
||||||
|
args: args{
|
||||||
|
reader: bytes.NewBuffer([]byte(`fl=31f118
|
||||||
|
h=1.1.1.1
|
||||||
|
ts=1683145336.383
|
||||||
|
visit_scheme=https
|
||||||
|
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
|
||||||
|
colo=PRG
|
||||||
|
sliver=none
|
||||||
|
http=http/2
|
||||||
|
loc=CZ
|
||||||
|
tls=TLSv1.3
|
||||||
|
sni=off
|
||||||
|
warp=off
|
||||||
|
gateway=off
|
||||||
|
rbi=off
|
||||||
|
kex=X25519`)),
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := cloudflareTraceResponseParser(tt.args.reader)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("cloudflareTraceResponseParser() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("cloudflareTraceResponseParser() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ func NewIfConfigMe(client Doer) *IfConfigMe {
|
|||||||
baseResolver: baseResolver{
|
baseResolver: baseResolver{
|
||||||
client: client,
|
client: client,
|
||||||
url: ifConfigMeUrl,
|
url: ifConfigMeUrl,
|
||||||
|
ipParser: defaultIpParser,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func NewV4IdentMe(client Doer) *V4IdentMe {
|
|||||||
baseResolver: baseResolver{
|
baseResolver: baseResolver{
|
||||||
client: client,
|
client: client,
|
||||||
url: v4IdentMeUrl,
|
url: v4IdentMeUrl,
|
||||||
|
ipParser: defaultIpParser,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user